How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/TerminalSkills/skills --skill preactBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Preact — Fast 3kB Alternative to React
Overview
Preact implements the same component model and hooks API as React in roughly 3kB (minified + gzipped), against React+ReactDOM's ~40kB+. It ships its own virtual DOM diffing, so most React code — useState, useEffect, useRef, useMemo, JSX — runs unchanged. On top of that, @preact/signals adds fine-grained reactive state: a signal's value can be read directly in JSX, and only the DOM text node that depends on it updates, skipping Preact's own diffing for that subtree. preact/compat is a shim that maps react and react-dom imports onto Preact, so most React component libraries (synthetic-event quirks aside) work without modification.
Instructions
Install
npm init preact my-app # scaffold a new Vite + Preact project (TS/router/ESLint prompts)
cd my-app && npm install
npm run devTo add Preact to an existing Vite project instead:
npm install preact
npm install -D @preact/preset-vite// vite.config.js
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
export default defineConfig({
plugins: [preact()],
});@preact/preset-vite configures the JSX pragma and automatically aliases react/react-dom to preact/compat, so React libraries work without a manual alias entry. Other bundlers (webpack, Rollup, Parcel, Jest) need the alias set by hand in their own config (resolve.alias, moduleNameMapper, etc.) pointing react and react-dom at preact/compat.
Components and Hooks
import { render } from "preact";
import { useState, useRef, useMemo } from "preact/hooks";
function TodoApp() {
const [todos, setTodos] = useState<{ id: number; text: string; done: boolean }[]>([]);
const [input, setInput] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
const remaining = useMemo(() => todos.filter((t) => !t.done).length, [todos]);
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput("");
inputRef.current?.focus();
};
const toggle = (id: number) =>
setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
return (
<div>
<h1>Todos ({remaining} remaining)</h1>
<input
ref={inputRef}
value={input}
onInput={(e) => setInput((e.target as HTMLInputElement).value)}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((t) => (
<li
key={t.id}
style={{ textDecoration: t.done ? "line-through" : "none" }}
onClick={() => toggle(t.id)}
>
{t.text}
</li>
))}
</ul>
</div>
);
}
render(<TodoApp />, document.getElementById("app")!);Signals
npm install @preact/signalsimport { signal, computed, effect, batch } from "@preact/signals";
// Global reactive state — no context provider needed
const count = signal(0);
const doubled = computed(() => count.value * 2);
effect(() => {
document.title = `Count: ${count.value}`;
});
function Counter() {
// Pass the signal itself in JSX: only this text node updates, the
// Counter component never re-renders on count changes.
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button
onClick={() =>
batch(() => {
count.value++;
})
}
>
+
</button>
</div>
);
}Inside a component, useSignal, useComputed, and useSignalEffect (from @preact/signals) create component-scoped equivalents of signal, computed, and effect.
Examples
Example 1: "Add a fine-grained reactive counter without re-rendering the whole app"
import { signal } from "@preact/signals";
import { render } from "preact";
const seconds = signal(0);
setInterval(() => seconds.value++, 1000);
function Clock() {
return <p>Elapsed: {seconds}s</p>;
}
render(<Clock />, document.getElementById("app")!);The <Clock> component itself never re-renders; Preact's signals binding patches only the text node inside the <p>.
Example 2: "Use a React charting library (Recharts) inside a Preact app"
// vite.config.js — alias is automatic with @preact/preset-vite
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
export default defineConfig({
plugins: [preact()],
});import { LineChart, Line, XAxis, YAxis } from "recharts";
const data = [
{ month: "Jan", revenue: 4200 },
{ month: "Feb", revenue: 5100 },
{ month: "Mar", revenue: 4800 },
];
function RevenueChart() {
return (
<LineChart width={400} height={240} data={data}>
<XAxis dataKey="month" />
<YAxis />
<Line type="monotone" dataKey="revenue" stroke="#22c55e" />
</LineChart>
);
}Recharts imports react/react-dom; with the Vite preset's automatic preact/compat alias it renders through Preact's DOM diffing with no code changes.
Guidelines
- Preact dispatches native DOM events, not React's synthetic event system — event objects and a few event-timing edge cases differ from React.
- Reading
signal.valueinside JSX text content avoids a full component re-render; destructuring a signal's.valueinto a local variable first loses that benefit and re-renders normally. preact/compatcovers most React libraries but not all — anything relying on React internals (react-reconciler, legacy context, some portals/suspense edge cases) may still break.- For server-side rendering, use
preact-render-to-string, or a Preact-based meta-framework; don't assume a Node.js SSR setup built for React works unchanged. - Don't add
@preact/signalsto a project that only needs plain hooks — it's an additional dependency and mental model; reach for it when cross-component reactive state or avoiding re-renders is the actual problem.

