Skip to main content

Writing Markup with JSX

Why JSX exists

In React, markup and the logic that renders it live together in one component. The old web split was by technology (HTML / CSS / JS in separate files); React splits by component instead, so a piece of UI and its logic stay in sync on every edit.

The 3 rules of JSX

JSX looks like HTML but is stricter.

1. One root element — a component returns a single parent. Wrap siblings in a tag or an empty Fragment <>…</>. (Why: JSX becomes JS objects, and a function can't return two objects unwrapped.)

2. Close every tag<img />, <li>item</li>. No unclosed tags.

3. camelCase attributesclassclassName, stroke-widthstrokeWidth, forhtmlFor. (Why: attributes become JS object keys, and class is a reserved word.) Exceptions: aria-* and data-* keep their dashes.

For converting a chunk of existing HTML, don't do it by hand — paste it into the HTML → JSX converter; it fixes all three rules automatically.

Under the hood — JSX is not HTML

JSX is JavaScript, not HTML — it compiles down to plain function calls.

Old ("classic") transformReact.createElement(...). This is why every file needed import React from 'react' even when I never referenced React directly — it had to be in scope:

jsx-compiles.jsx
// I write
<h1>Hello</h1>
// compiles to
React.createElement('h1', null, 'Hello')

New ("automatic") transform (React 17+) → the compiler auto-imports a jsx() helper from react/jsx-runtime, so I no longer need to import React just to use JSX:

jsx-runtime.jsx
import { jsx as _jsx } from 'react/jsx-runtime';
_jsx('h1', { children: 'Hello' })

Notice the lowercase 'h1' passed as a string — that's the same case rule from components: lowercase = host element (string), Capitalized = a component (variable).

Full detail: Introducing the New JSX Transform.