JSX: the rules that matter
Where JSX differs from HTML and why: braces for expressions, className instead of class, one root per return, and attributes that are JavaScript values.
Braces are the way back to JavaScript
Inside JSX, curly braces open a JavaScript expression: {name}, {2 + 3}, {items.length}, {user.age > 18 ? "adult" : "minor"}. Anything that produces a value can go there; a statement like if cannot, because JSX is building one value, not running a program.
Every expression must produce something printable — a string, a number, an element, an array of elements. null and false render nothing at all, which is a fact the conditional-rendering chapter leans on heavily.
Where it differs from HTML
JSX looks like HTML because that is the point — but it is JavaScript, so a few names differ. class becomes className, because class means something else already. for on a label becomes htmlFor. Every attribute is written in camelCase: onclick becomes onClick, tabindex becomes tabIndex.
Empty elements close themselves: <br />, <img src="..." />, <input />. And a return needs one single element, because a function returns one value — to return siblings, wrap them in an element or in a fragment: <> and </> with nothing between, which makes no extra element on the page.
Attributes, style, comments
Most attributes take a string in quotes. The style attribute is different: it takes an object, so the braces double up — style={{ color: "teal" }} — the outer pair is JSX saying "expression", the inner pair is a JavaScript object literal. Property names there are camelCase too: fontSize, backgroundColor.
A comment inside JSX also lives in braces: {/* like this */}. Outside the markup, in ordinary JavaScript, // works as usual.
Text next to an expression
Text and braces can sit side by side in the same element: <p>Hello, {name}!</p> is one paragraph made of a string, a value and another string. React inserts each piece in order, so the punctuation has to be outside the braces. Hello, {name}! and Hello,{name}! differ by a space, and that space is not a detail — it is part of the text.
Whitespace at the start and end of a line inside JSX is trimmed, and a line break between two pieces of text becomes a single space. When the exact spacing matters, put it in a string or use {" "}.
A tag name is a decision
A lowercase tag such as <div> or <span> becomes that HTML element. A capitalised tag such as <Card /> is looked up as a value in scope — the function or class you defined. That is the whole rule, and it explains both the error when you forget the capital and the freedom to name your own components anything you like.
A dotted name such as <UI.Card /> reads a property of an object, which is how a file of related components is usually exported.
All the rules in one element
const user = { name: 'Ada', visits: 3, returning: true };
const card = (
<>
{/* one root per return: the fragment above makes the siblings legal */}
<h2 style={{ color: 'teal', fontSize: '1.5rem' }}>
Welcome back, {user.name}
</h2>
<p className="note">
Visits so far: {user.visits}. Status:{' '}
{user.returning ? 'returning reader' : 'first time'}
</p>
<label htmlFor="email">Email</label>
<input id="email" type="email" placeholder="[email protected]" />
<hr />
</>
);What the transform actually produces
The JSX you write is not sent to the browser as markup. A transform rewrites each tag into a call — <h1 className="title">Hi</h1> becomes React.createElement('h1', { className: 'title' }, 'Hi') — and the result is a plain object describing an element.
That is why attributes are JavaScript values and why the rules are not arbitrary. className is a property name, {2 + 3} is an argument, and a fragment is a shorter call that produces the children without a wrapper element.
The rules worth memorising
A return hands back one value, so one root element or fragment. Void elements close themselves because there is no closing tag to pair: <input />, <img />, <br />. A comment inside markup goes in braces, because otherwise the parser would treat it as text.
Attributes split into two groups: strings in quotes, and everything else in braces. title="Hello" is a string; count={3} is a number; disabled alone means true. style is always the second kind, and its keys are camelCase because they become properties on an object.
The three that bite most often: writing class and getting a warning instead of a style; passing style="color: teal" as a string, when React expects an object; and spelling an event prop onclick, which React ignores because it expects onClick. The console names all three, so read it before hunting through the markup.
Try these next
- Add a
<label>and an<input>pair, joined byhtmlForandid. - Put a
{/* comment */}between two elements and confirm it leaves no text. - Move one style property into a constant object above the return and pass that object instead.
A profile card in JSX
Run it, then add a line showing whether the member is an adult, using a ternary in braces. Try using `class` instead of `className` in the JSX and read what the preview says.
A settings row in JSX
Run it, then add a third row with its own checkbox and label. Give it the same style object, join the label with `htmlFor` and `id`, and keep the input self-closing.
Two JSX elements side by side with no wrapper is a syntax error, not a warning — "Adjacent JSX elements must be wrapped in an enclosing tag". The fragment is the wrapper that adds nothing to the page.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Why is it className and not class?
