JSX — what it actually compiles to
JSX looks like HTML sitting inside JavaScript, which makes it easy to assume it's some kind of template syntax React parses at runtime. It isn't — it's compiled away entirely before your code ever runs, into plain function calls.
3 min read
JSX is not valid JavaScript on its own — it needs a compiler
const element = <h1 className="title">Hello, world!</h1>;Browsers don't understand this syntax at all — <h1> isn't valid inside a .js file as far as JavaScript's own grammar is concerned. A build tool (Babel, or the TypeScript compiler with JSX support, or the compiler built into Next.js/Vite) transforms every JSX expression into plain JavaScript before the code ever reaches a browser or Node — this is the exact same "compile-time transformation, nothing survives at runtime" relationship the TypeScript domain covers for type annotations, just transforming syntax instead of erasing types.
What it actually compiles to: nested function calls
const element = <h1 className="title">Hello, world!</h1>;// What the above actually becomes after compilation
const element = React.createElement("h1", { className: "title" }, "Hello, world!");React.createElement(type, props, ...children) is a plain JavaScript function. <h1 className="title">Hello, world!</h1> is nothing more than syntax sugar for calling it with "h1" as the element type, { className: "title" } as its props, and "Hello, world!" as its child content. This is the single fact that demystifies most of what looks like JSX "magic" — nested JSX just becomes nested createElement calls, each one describing one node in the tree React will eventually reconcile against the real DOM.
{}: the one escape hatch back into real JavaScript
const name = "Ada";
const items = ["a", "b", "c"];
const element = (
<div>
<p>Hello, {name}!</p>
<p>You have {items.length} items</p>
<p>{1 + 1}</p>
</div>
);Curly braces {} inside JSX drop back into plain JavaScript expression evaluation — whatever's inside is evaluated as a real expression, and the resulting value is inserted into the tree. This works for a variable ({name}), a computed value ({items.length}), or any expression at all ({1 + 1}) — the one hard rule is that it must be an expression (something that produces a value), not a statement (like an if block or a for loop, neither of which "returns" anything) — this is exactly why conditional rendering in JSX reaches for the ternary operator or && instead of a plain if, covered in this domain's props lesson.
Every JSX element must have exactly one root
// Error: JSX expressions must have one parent element
function Broken() {
return (
<h1>Title</h1>
<p>Paragraph</p>
);
}
// Fixed: wrapped in a single parent
function Fixed() {
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
}Because JSX compiles to a single createElement(...) call (or, for a component, a single function return value), a component can only return one root element — two sibling elements with no shared parent has no single call to compile down into. <>...</> (a Fragment, shorthand for React.Fragment) exists specifically to satisfy this "exactly one root" requirement without adding a real, unwanted <div> wrapper to the actual rendered DOM — Fragments themselves produce no DOM node at all.
className, not class — and why the difference exists at all
// HTML: <div class="card">
// JSX:
<div className="card">class is a reserved word in JavaScript (used for actual class declarations), so JSX can't reuse it as a prop name the way HTML uses it as an attribute name — className is the JSX equivalent, which then compiles to setting the real DOM's className property. This is the most common individual "gotcha" for anyone coming from writing plain HTML — a handful of other attributes differ the same way (for becomes htmlFor, for the identical reason: for is also a reserved word), but className is by far the one encountered first and most often.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does `<h1 className="title">Hello</h1>` actually compile to?
2. Why must the content inside `{}` in JSX be an expression, not a statement (like an if block)?
3. Why does JSX use `className` instead of `class`?