Skip to content
Back to skills

Preact

ASecurity

Preact is a 3kB alternative to React with the same modern API — components, hooks, and JSX — plus first-class fine-grained reactivity through `@preact/signals`. Use when a user wants to build a performance-critical web app, embedded widget, or mobile-first site with a smaller bundle than React, wants to add reactive signals state, or wants to reuse an existing React component library on top of Preact via the `preact/compat` layer.

  • 142 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 6, 2026
developmentgobashreactnodegitapifrontendperformance

Works with

  • terminal
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill preact --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Preact?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Preact
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-preact/badge)](https://www.skillsdirectory.com/skills/terminalskills-preact)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: preact
description: >-
  Preact is a 3kB alternative to React with the same modern API — components,
  hooks, and JSX — plus first-class fine-grained reactivity through
  `@preact/signals`. Use when a user wants to build a performance-critical web
  app, embedded widget, or mobile-first site with a smaller bundle than React,
  wants to add reactive signals state, or wants to reuse an existing React
  component library on top of Preact via the `preact/compat` layer.
license: Apache-2.0
compatibility: "Preact 10.x or 11.x, Node.js 18+ for tooling"
metadata:
  author: terminal-skills
  version: "1.1.0"
  category: development
  tags:
    - preact
    - react-alternative
    - signals
    - jsx
    - frontend
  repository: https://github.com/preactjs/preact
---

# 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

```bash
npm init preact my-app        # scaffold a new Vite + Preact project (TS/router/ESLint prompts)
cd my-app && npm install
npm run dev
```

To add Preact to an existing Vite project instead:

```bash
npm install preact
npm install -D @preact/preset-vite
```

```js
// 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

```tsx
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

```bash
npm install @preact/signals
```

```tsx
import { 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"

```tsx
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"

```js
// 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()],
});
```

```tsx
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.value` inside JSX text content avoids a full component re-render; destructuring a signal's `.value` into a local variable first loses that benefit and re-renders normally.
- `preact/compat` covers 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/signals` to 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.

Files in this skill

  • SKILL.md4.6 KB
  • _scores.json1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…