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 attributes — class → className, stroke-width → strokeWidth, for → htmlFor. (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") transform → React.createElement(...). This is why every file needed import React from 'react' even when I never referenced React directly — it had to be in scope:
// 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:
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.