Skip to main content
JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside JavaScript files. Preact supports JSX and provides a lightweight implementation.

What is JSX?

JSX allows you to write UI components using familiar HTML syntax:
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}
This is more readable than the equivalent JavaScript:
function Greeting({ name }) {
  return createElement('h1', null, 'Hello, ', name, '!');
}

How JSX works

JSX is not valid JavaScript. It must be transformed by a compiler (like Babel or TypeScript) into function calls.

The createElement function

Preact’s createElement() function (aliased as h()) converts JSX into VNodes (src/create-element.js:16):
export function createElement(type, props, children) {
  let normalizedProps = {},
    key,
    ref,
    i;
  for (i in props) {
    if (i == 'key') key = props[i];
    else if (i == 'ref' && typeof type != 'function') ref = props[i];
    else normalizedProps[i] = props[i];
  }

  if (arguments.length > 2) {
    normalizedProps.children =
      arguments.length > 3 ? slice.call(arguments, 2) : children;
  }

  return createVNode(type, normalizedProps, key, ref, NULL);
}

JSX transformation

This JSX:
<div className="container">
  <h1>Hello</h1>
  <p>Welcome to Preact</p>
</div>
Gets transformed to:
import { h } from 'preact';

h('div', { className: 'container' },
  h('h1', null, 'Hello'),
  h('p', null, 'Welcome to Preact')
);
The h function is Preact’s alias for createElement, inspired by hyperscript conventions.

Configuring JSX

You need to configure your build tool to transform JSX and specify Preact as the JSX factory.

Babel configuration

Add the React JSX transform plugin to your .babelrc or babel.config.js:
{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "h",
      "pragmaFrag": "Fragment"
    }]
  ]
}
With this configuration, you need to import h in every file that uses JSX:
import { h } from 'preact';

function App() {
  return <div>Hello</div>;
}

TypeScript configuration

Configure TypeScript in your tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment",
    "target": "es2018",
    "module": "es2015",
    "moduleResolution": "node",
    "paths": {
      "preact": ["node_modules/preact/src/index.js"]
    }
  }
}
TypeScript requires you to import h even if you don’t use it directly, as it’s referenced in the transpiled output.

Automatic JSX runtime

Modern build tools support the automatic JSX runtime, which eliminates the need to import h:
.babelrc
{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "runtime": "automatic",
      "importSource": "preact"
    }]
  ]
}
With automatic runtime, you can write JSX without any imports:
// No imports needed!
function App() {
  return <div>Hello</div>;
}

JSX expressions

You can embed any JavaScript expression in JSX using curly braces:
function UserInfo({ user }) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Age: {user.age}</p>
      <p>Email: {user.email.toLowerCase()}</p>
      <p>Member since: {new Date(user.joined).getFullYear()}</p>
    </div>
  );
}

Conditional rendering

function Status({ isOnline }) {
  return (
    <div>
      {isOnline ? <span>Online</span> : <span>Offline</span>}
    </div>
  );
}

JSX attributes

JSX attributes use camelCase naming, except for data-* and aria-* attributes:
function Example() {
  return (
    <div
      className="container"
      onClick={handleClick}
      data-id="123"
      aria-label="Example"
      style={{ color: 'red', fontSize: '16px' }}
    >
      Content
    </div>
  );
}
Use className instead of class, as class is a reserved keyword in JavaScript.

Special attributes

Preact handles several attributes specially:
  • className - Sets the element’s CSS class
  • style - Accepts an object of CSS properties
  • dangerouslySetInnerHTML - Sets raw HTML (use with caution)
  • key - Helps Preact identify elements in lists
  • ref - Gets a reference to the DOM node

Children

Everything between an opening and closing tag becomes the children prop:
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

// Usage
<Card>
  <h2>Title</h2>
  <p>Content here</p>
</Card>
Children can be:
  • Strings and numbers
  • JSX elements
  • Arrays of elements
  • Functions (render props)
  • null, undefined, or false (renders nothing)

Fragments

Fragments let you group multiple elements without adding extra DOM nodes (src/create-element.js:77):
export function Fragment(props) {
  return props.children;
}

Using fragments

import { Fragment } from 'preact';

function List() {
  return (
    <Fragment>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
    </Fragment>
  );
}
The <> short syntax requires configuring your build tool to recognize it as a Fragment.

Lists and keys

When rendering lists, each element should have a unique key prop:
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}
Keys help Preact identify which items have changed:
todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))

Real-world example

Here’s a complete example from Preact’s demo (demo/index.jsx:68):
import { Component, Fragment } from 'preact';
import { Router, Link } from 'preact-router';

class App extends Component {
  render({ url }) {
    return (
      <div class="app">
        <header>
          <nav>
            <Link href="/" activeClassName="active">
              Home
            </Link>
            <Link href="/todo" activeClassName="active">
              ToDo
            </Link>
            <Link href="/context" activeClassName="active">
              Context
            </Link>
          </nav>
        </header>
        <main>
          <Router url={url}>
            <Home path="/" />
            <Todo path="/todo" />
            <Context path="/context" />
          </Router>
        </main>
      </div>
    );
  }
}
This example demonstrates:
  • Class component with JSX
  • Props destructuring in render
  • Nested JSX elements
  • Component composition
  • Conditional rendering via Router

JSX gotchas

Self-closing tags

Always close tags, even for elements that don’t have children:
<input />
<br />
<img src="photo.jpg" />

JavaScript reserved words

Some HTML attributes conflict with JavaScript keywords:
  • Use className instead of class
  • Use htmlFor instead of for

Comments

Use JavaScript comments inside JSX expressions:
function Example() {
  return (
    <div>
      {/* This is a comment */}
      <p>Content</p>
    </div>
  );
}

Build docs developers (and LLMs) love