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 );
}
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:
{
"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:
{
"plugins" : [
[ "@babel/plugin-transform-react-jsx" , {
"runtime" : "automatic" ,
"importSource" : "preact"
}]
]
}
{
"compilerOptions" : {
"jsx" : "react-jsx" ,
"jsxImportSource" : "preact"
}
}
require ( 'esbuild' ). build ({
jsx: 'automatic' ,
jsxImportSource: 'preact' ,
// ... other options
});
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
Ternary operator
Logical AND
Null for nothing
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
Fragment component
Short syntax
import { Fragment } from 'preact' ;
function List () {
return (
< Fragment >
< li > Item 1 </ li >
< li > Item 2 </ li >
< li > Item 3 </ li >
</ Fragment >
);
}
function List () {
return (
<>
< li > Item 1 </ li >
< li > Item 2 </ li >
< li > Item 3 </ li >
</>
);
}
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:
Good - Unique IDs
Bad - Array indices
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
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
Use JavaScript comments inside JSX expressions:
function Example () {
return (
< div >
{ /* This is a comment */ }
< p > Content </ p >
</ div >
);
}