Lightweight Library

Preact

Preact is a 3kB alternative to React with the same modern API. Same components, same hooks, a fraction of the size — ideal when every kilobyte counts.

intermediate12 min readUpdated Sep 15, 2026
Counter.jsx
jsx
// Counter.jsx
import { useState } from "preact/hooks";

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}
Size
Around 3kB gzipped
API
React-compatible hooks
Rendering
Virtual DOM
Signals
First-class via @preact/signals
Ecosystem
preact/compat bridge
Great for
Widgets and performance budgets

Why it matters

Why choose Preact

Tiny by default

The whole library is a few kilobytes, which leaves more of your performance budget for your own code and content.

Familiar API

Components, hooks, context and refs work the way you expect, so React knowledge transfers almost directly.

Signals built in

The official signals package adds fine-grained reactivity and can update state without re-rendering a component.

The big picture

The three ideas behind Preact

A tiny core, an API you already know, and an optional bridge to the React ecosystem.

The core

Small runtime

A compact virtual DOM implementation with the component and hooks API you already know.

Signals

Reactivity

An official package that tracks dependencies and updates only what changed.

The bridge

Ecosystem

preact/compat lets most React libraries and components run on Preact unchanged.

Preact at a glance

The core of Preact

Components

Function components with props, children and composition.

Hooks

useState, useEffect, useRef and friends from preact/hooks.

Signals

Reactive values that update the DOM directly, even outside components.

Context

Share values across the tree with createContext and useContext.

preact/compat

Alias React to Preact and reuse the React ecosystem.

htm

An optional tagged template that removes the need for a build step.

A short history

Small by design

  1. 2013

    Started as a React alternative

    Jason Miller builds a smaller virtual DOM library with a compatible API.

    13
  2. 2015

    Preact 3

    The library stabilises and gains a following for its size and speed.

    15
  3. 2019

    Preact X and hooks

    Full hooks support and the compat layer make React code portable.

    19
  4. 2022

    Signals

    The official signals package brings fine-grained reactivity to Preact.

    22
  5. Today

    The lightweight standard

    A common choice for widgets, embedded UIs and strict performance budgets.

    Today

The complete guide

Preact: Everything you need to know

What is Preact?

Preact is a tiny alternative to React. It implements the same component model, the same hooks and a virtual DOM, in around three kilobytes gzipped. For most components, the code you write for React works on Preact with little or no change.

That size is the whole point. When you are embedding a widget in a page you do not fully control, building a performance-critical interface or simply want to keep your JavaScript budget small, a few kilobytes of framework can make a measurable difference. Preact gives you the familiar model without the weight.

Components and hooks

Preact components are functions that return JSX, and hooks come from preact/hooks.

// Search.jsx
import { useState } from "preact/hooks";

export function Search({ onSearch }) {
  const [query, setQuery] = useState("");

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        onSearch(query);
      }}
    >
      <input
        value={query}
        onInput={(event) => setQuery(event.currentTarget.value)}
      />
    </form>
  );
}

useState, useEffect, useRef, useContext, useMemo and useCallback all behave as you would expect. The main differences are small: Preact supports both class and className, and uses native event names such as onInput and onDblClick.

Preact Signals

Signals are Preact’s first-class reactivity primitive. A signal holds a value and, when read inside JSX, subscribes that part of the DOM to it. Changing the value updates the DOM directly — no component re-render required.

// Counter.jsx
import { signal, computed } from "@preact/signals";

const count = signal(0);
const double = computed(() => count.value * 2);

export function Counter() {
  return (
    <div>
      <button onClick={() => count.value++}>
        Count: {count}
      </button>
      <p>Double: {double}</p>
    </div>
  );
}

Because signals live outside the component tree, you can share and mutate them from anywhere, including plain modules and event handlers. They are the recommended way to manage state that changes often.

The compat layer

The preact/compat package maps React’s API onto Preact. Alias react and react-dom to it in your bundler, and most React libraries work unchanged.

// vite.config.js
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";

export default defineConfig({
  plugins: [preact()],
  resolve: {
    alias: {
      react: "preact/compat",
      "react-dom": "preact/compat",
      "react-dom/client": "preact/compat/client",
    },
  },
});

This is how Preact projects use the React ecosystem — component libraries, routing, state managers and more — while still shipping the smaller runtime. The trade-off is a little overhead from the compatibility layer, but it is still far smaller than React itself.

No build step with htm

If you want to skip the build entirely, the htm library provides a tagged template alternative to JSX.

// app.js
import { h, render } from "preact";
import htm from "htm";

const html = htm.bind(h);

function App({ name }) {
  return html`<h1>Hello, ${name}!</h1>`;
}

render(html`<${App} name="Ada" />`, document.body);

This is handy for progressive enhancement, quick demos and embedding a small interactive island into a server-rendered page.

Differences from React

The overlap is large, but a few details differ:

  • Preact uses native DOM event names, so onInput and onDblClick replace some React-specific names.
  • Both class and className work, and both for and htmlFor are accepted.
  • There is no synthetic event pooling, because Preact never pooled events.
  • Some newer React features, such as parts of the server-components API, are not implemented.
  • TypeScript JSX types come from Preact, and need aliasing when using compat.

For everyday components and hooks, you will rarely notice the difference.

When to use Preact

Preact shines when size and startup cost are priorities: embeddable widgets, marketing pages with a little interactivity, content sites, and applications with strict performance budgets. It is also a gentle migration target for an existing React codebase that needs to get smaller.

If you rely on React-specific tooling, bleeding-edge features or a library that depends on React internals, staying on React may be less friction. The good news is that the API overlap means the decision is not a one-way door.

Best practices

  • Use signals for state that changes frequently or is shared outside the tree.
  • Reach for preact/compat only when you need React ecosystem packages.
  • Alias React types as well as runtime modules when using compat.
  • Keep components small and avoid unnecessary re-renders by reading signals at the point of use.
  • Measure the real bundle impact — the savings are meaningful, but your own code still dominates.
  • Prefer preact/preset-vite to configure the build correctly out of the box.

Common mistakes

  • Mixing React and Preact packages without the compat alias.
  • Forgetting to alias react-dom/client, which breaks the new root API.
  • Assuming every React library works without testing the compat layer.
  • Overusing signals and re-rendering components that could read a signal directly.
  • Treating Preact as feature-identical to React and being surprised by an edge case.
  • Adding Preact for size, then pulling in a large dependency that erases the gain.

Where to go next

Preact is the pragmatic answer when React’s model is right but its size is not. Compare it with React and Svelte, wire it up with Vite, and keep your JavaScript fundamentals close. Then try embedding a small Preact widget in a plain HTML page to see how little it costs.

Reactive state

Signals update the DOM directly and can be read outside components, without re-rendering.

Signals
import { signal } from "@preact/signals";

const count = signal(0);

<button onClick={() => count.value++}>
  {count}
</button>
useState
import { useState } from "preact/hooks";

const [count, setCount] = useState(0);

<button onClick={() => setCount(count + 1)}>
  {count}
</button>

Using React libraries

With the compat alias, React imports resolve to Preact, so most ecosystem packages work unchanged.

Prefer
// vite.config.js
resolve: {
  alias: {
    react: "preact/compat",
    "react-dom": "preact/compat",
  },
}
Avoid
// rewriting every import
// by hand across the codebase
import React from "react";
import { render } from "react-dom";

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Preact?

Our interactive tutorial walks you through Preact step by step — with quizzes and real code you can run in the browser.