Skip to main content
This guide will walk you through creating your first Preact application step by step. By the end, you’ll have a working interactive component that demonstrates Preact’s core features.

Prerequisites

Before you begin, make sure you have:
  • Node.js 20.0 or later installed
  • A package manager (npm, yarn, or pnpm)
  • A text editor or IDE
  • Basic knowledge of JavaScript and HTML

Create your first app

1

Set up your project

Create a new directory and initialize a package.json file:
mkdir my-preact-app
cd my-preact-app
npm init -y
Install Preact:
npm install preact
2

Create an HTML file

Create an index.html file as your entry point:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Preact App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/index.js"></script>
</body>
</html>
3

Create your first component

Create an index.js file with a simple component:
index.js
import { h, render } from 'preact';

const App = () => {
  return (
    <div>
      <h1>Hello from Preact!</h1>
      <p>This is your first component.</p>
    </div>
  );
};

render(<App />, document.getElementById('app'));
Note: To use JSX syntax, you’ll need a build tool. For now, you can use the h function directly:
index.js
import { h, render } from 'preact';

const App = () => {
  return h('div', null,
    h('h1', null, 'Hello from Preact!'),
    h('p', null, 'This is your first component.')
  );
};

render(h(App), document.getElementById('app'));
4

Add state with hooks

Make your component interactive by adding state with the useState hook:
index.js
import { h, render } from 'preact';
import { useState } from 'preact/hooks';

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

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrement
      </button>
      <button onClick={() => setCount(0)}>
        Reset
      </button>
    </div>
  );
};

render(<Counter />, document.getElementById('app'));
Without JSX:
index.js
import { h, render } from 'preact';
import { useState } from 'preact/hooks';

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

  return h('div', null,
    h('h1', null, `Counter: ${count}`),
    h('button', { onClick: () => setCount(count + 1) }, 'Increment'),
    h('button', { onClick: () => setCount(count - 1) }, 'Decrement'),
    h('button', { onClick: () => setCount(0) }, 'Reset')
  );
};

render(h(Counter), document.getElementById('app'));
5

Add effects

Use the useEffect hook to perform side effects:
index.js
import { h, render } from 'preact';
import { useState, useEffect } from 'preact/hooks';

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

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <p>The page title updates with the count!</p>
    </div>
  );
};

render(<App />, document.getElementById('app'));
6

Build with components

Break your UI into reusable components:
index.js
import { h, render } from 'preact';
import { useState } from 'preact/hooks';

const Button = ({ onClick, children }) => (
  <button 
    onClick={onClick}
    style={{ padding: '10px', margin: '5px' }}
  >
    {children}
  </button>
);

const Display = ({ value }) => (
  <div style={{ fontSize: '2em', margin: '20px' }}>
    Count: {value}
  </div>
);

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

  return (
    <div>
      <h1>Component-based Counter</h1>
      <Display value={count} />
      <div>
        <Button onClick={() => setCount(count + 1)}>
          +
        </Button>
        <Button onClick={() => setCount(count - 1)}>
          -
        </Button>
        <Button onClick={() => setCount(0)}>
          Reset
        </Button>
      </div>
    </div>
  );
};

render(<App />, document.getElementById('app'));

Working with class components

While functional components with hooks are recommended, Preact also supports class components:
import { Component, render } from 'preact';

class Counter extends Component {
  state = { count: 0 };

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };

  render() {
    return (
      <div>
        <h1>Counter: {this.state.count}</h1>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

render(<Counter />, document.getElementById('app'));

Component lifecycle

Class components have lifecycle methods for different stages:
import { Component, render } from 'preact';

class LifecycleDemo extends Component {
  state = { data: null };

  componentDidMount() {
    // Called after component is mounted
    console.log('Component mounted');
    this.fetchData();
  }

  componentDidUpdate(prevProps, prevState) {
    // Called after component updates
    console.log('Component updated');
  }

  componentWillUnmount() {
    // Called before component is unmounted
    console.log('Component will unmount');
  }

  fetchData = async () => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    this.setState({ data });
  };

  render() {
    return (
      <div>
        {this.state.data ? (
          <pre>{JSON.stringify(this.state.data, null, 2)}</pre>
        ) : (
          <p>Loading...</p>
        )}
      </div>
    );
  }
}

render(<LifecycleDemo />, document.getElementById('app'));
With functional components, use hooks instead:
import { h, render } from 'preact';
import { useState, useEffect } from 'preact/hooks';

const LifecycleDemo = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    // Runs after mount and updates
    console.log('Component mounted or updated');

    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      setData(data);
    };

    fetchData();

    // Cleanup function (runs before unmount)
    return () => {
      console.log('Component will unmount');
    };
  }, []); // Empty array = run only on mount

  return (
    <div>
      {data ? (
        <pre>{JSON.stringify(data, null, 2)}</pre>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

render(<LifecycleDemo />, document.getElementById('app'));

Complete example

Here’s a complete todo app that demonstrates multiple concepts:
import { h, render } from 'preact';
import { useState } from 'preact/hooks';

const TodoItem = ({ todo, onToggle, onDelete }) => (
  <li style={{ margin: '10px 0' }}>
    <label>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ 
        textDecoration: todo.completed ? 'line-through' : 'none',
        marginLeft: '10px'
      }}>
        {todo.text}
      </span>
    </label>
    <button 
      onClick={() => onDelete(todo.id)}
      style={{ marginLeft: '10px' }}
    >
      Delete
    </button>
  </li>
);

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

  const addTodo = (e) => {
    e.preventDefault();
    if (!input.trim()) return;

    setTodos([
      ...todos,
      {
        id: Date.now(),
        text: input,
        completed: false
      }
    ]);
    setInput('');
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <h1>Todo List</h1>
      <form onSubmit={addTodo}>
        <input
          type="text"
          value={input}
          onInput={(e) => setInput(e.target.value)}
          placeholder="What needs to be done?"
          style={{ padding: '8px', fontSize: '16px' }}
        />
        <button type="submit" style={{ padding: '8px 16px', marginLeft: '10px' }}>
          Add
        </button>
      </form>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {todos.map(todo => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={toggleTodo}
            onDelete={deleteTodo}
          />
        ))}
      </ul>
      <p>
        {todos.filter(t => !t.completed).length} of {todos.length} items remaining
      </p>
    </div>
  );
};

render(<TodoApp />, document.getElementById('app'));

Key concepts recap

Render function

Use render() to mount components to the DOM. It accepts a component and a parent DOM element.

useState hook

Add state to functional components. Returns current state and a setter function.

useEffect hook

Perform side effects like data fetching, subscriptions, or DOM manipulation.

Component composition

Break your UI into small, reusable components that accept props and return JSX.

Next steps

Now that you’ve built your first Preact app, explore more features:

Learn about hooks

Dive deeper into hooks for state management and side effects

Explore the API

Browse the complete Preact API documentation

Server-side rendering

Learn how to render Preact components on the server

React compatibility

Use React libraries with preact/compat

Build docs developers (and LLMs) love