JSX — Markup That Is Really JavaScript
BeginnerJSX compiles to function calls. Knowing that explains every rule it has, including the ones that look arbitrary.
Overview
JSX looks like HTML inside JavaScript, which makes people treat it as a template language with strange rules. It is not a template language — it is syntax sugar over function calls, and every element you write becomes a call producing a plain object. That is why attributes use JavaScript names rather than HTML ones, why you can only interpolate expressions and not statements, and why a component must return a single root. None of these are arbitrary once you have seen what JSX compiles into.
What It Compiles To
One transformation explains the whole syntax.
// You write
<button className="primary" onClick={handleRun}>Run</button>
// The compiler produces
jsx('button', { className: 'primary', onClick: handleRun, children: 'Run' })
// which evaluates to a plain object describing what to render:
{ type: 'button', props: { className: 'primary', ... } }
// Consequences that follow directly:
// - 'class' is a reserved word -> className
// - 'for' is a reserved word -> htmlFor
// - attributes are JS properties -> onClick, tabIndex, readOnly
// - a component returns ONE value -> one root element, or a Fragment
<> {/* Fragment — groups without a wrapper div */}
<Header />
<Main />
</>
// A capital letter means "component"; lowercase means "DOM element".
<button /> // the HTML button
<Button /> // your componentExpressions and Conditionals
Braces take an expression, never a statement. That constrains how you write conditionals inside markup.
<p>{user.name}</p>
<p>{items.length > 0 ? 'Ready' : 'Empty'}</p>
<p>{formatDate(attempt.submittedAt)}</p>
// if / for / switch are statements — they go ABOVE the return
if (!user) return <SignInPrompt />
// Conditional rendering, with the classic trap
{count && <Badge count={count} />} // renders "0" when count is 0
{count > 0 && <Badge count={count} />} // correct
{items.length ? <List /> : <Empty />} // also fine
// These render nothing at all: null, undefined, false, true, ''
// A number does render, which is why 0 leaks onto the screen.
// Attributes take expressions too
<img src={problem.cover} alt={problem.title} />
<button disabled={isSubmitting}>Save</button>
<div className={`card ${isActive ? 'active' : ''}`} />
<Row {...problem} /> // spread propsRules Worth Knowing Early
A short list that covers most JSX confusion.
// Comments inside JSX need braces
{/* like this */}
// style takes an object with camelCase keys, not a string
<div style={{ marginTop: 8, backgroundColor: 'red' }} />
// Self-close void elements
<img /> <br /> <input />
// Whitespace and newlines between elements are collapsed;
// explicit spaces use {' '}
<span>Solved{' '}<strong>42</strong></span>
// Rendering raw HTML is deliberately awkward, because it is unsafe
<div dangerouslySetInnerHTML={{ __html: sanitised }} />
// Everything else is escaped automatically:
<p>{userComment}</p> // safe — script tags render as textKey Points to Remember
- 1JSX compiles to function calls returning plain objects — every syntax rule follows from that
- 2className and htmlFor exist because class and for are reserved JavaScript words
- 3Braces accept expressions only; if, for and switch go above the return statement
- 4{count && <Badge/>} renders a literal 0 when count is 0 — compare explicitly instead
- 5React escapes interpolated values by default; dangerouslySetInnerHTML is the deliberate exception
Interview Questions
Sign in to ask AriaWhat does JSX compile to, and why does that matter?
Why does {items.length && <List/>} sometimes render a 0?
Why must a component return a single root element, and how do Fragments help?
Ask Aria about JSX — Markup That Is Really JavaScript
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.