JSX: Writing Markup in JavaScript
JSX is a syntax extension to JavaScript that lets you write HTML-like markup directly inside a JavaScript file. It is the single most recognisable feature of React — and one of the most misunderstood. JSX is not a string, not a template language, and not actual HTML. It is syntactic sugar that gets compiled into plain JavaScript function calls before the browser ever sees it.
What JSX Actually Is
Before JSX existed, building React UIs meant calling React.createElement() by hand for every element. That worked but was tedious and hard to read:
// Without JSX — pure React.createElement calls
const element = React.createElement(
'div',
{ className: 'card' },
React.createElement('h2', null, 'Hello, world!'),
React.createElement('p', null, 'Welcome to React.')
)JSX is a shorthand that compiles to exactly the same code. Here is the identical UI written with JSX:
// With JSX — compiled to the createElement calls above
const element = (
<div className="card">
<h2>Hello, world!</h2>
<p>Welcome to React.</p>
</div>
)The Babel compiler (or the TypeScript compiler with jsx: "react-jsx") transforms every JSX element into a React.createElement() call at build time. No JSX ever reaches the browser — only the compiled JavaScript does.
The Compilation Step in Detail
To build an intuition for what JSX compiles to, it helps to see the one-to-one mapping between JSX syntax and the function call arguments:
// JSX input
<Button type="submit" disabled={false}>
Save changes
</Button>
// What the compiler produces (new JSX transform)
import { jsx as _jsx } from 'react/jsx-runtime'
_jsx(Button, {
type: 'submit',
disabled: false,
children: 'Save changes',
})The three arguments map to: the type (string for HTML elements, function/class for components), the props object (including children), and — in the classic transform — optional child arguments. Everything you put in JSX, the compiler faithfully converts into a plain object tree.
Why JSX Exists
React's core insight is that rendering logic and markup are inherently coupled. In traditional web development they were separated — HTML in .html files, JavaScript in .js files — but in practice a button's rendering and its click handler belong together because they change together. JSX makes that co-location natural:
function SubmitButton({ isLoading, onSubmit }) {
return (
<button
type="submit"
onClick={onSubmit}
disabled={isLoading}
className={isLoading ? 'btn btn--loading' : 'btn'}
>
{isLoading ? 'Saving…' : 'Save changes'}
</button>
)
}The markup, the conditional class, the event handler, and the loading text are all in one place. Changing the button requires touching one file, not three.
JSX is Not HTML
JSX looks like HTML but has important differences. The most common surprises for new React developers are:
HTML | JSX | Reason |
|---|---|---|
class="container" | className="container" |
|
for="email" | htmlFor="email" |
|
<br> | <br /> | All JSX tags must be closed |
<img src="..."> | <img src="..." /> | Self-closing tags require the slash |
onclick="handler()" | onClick={handler} | Events are camelCase props, not strings |
style="color:red" | style={{ color: "red" }} | Style is a JS object, not a string |
<!-- comment --> | {/* comment */} | HTML comments are invalid in JSX |
JSX Must Return One Root Element
Because JSX compiles to a function call that returns a single value, every JSX expression must have exactly one root element. Returning two sibling elements without a wrapper is a syntax error:
// ❌ Error: Adjacent JSX elements must be wrapped
function Broken() {
return (
<h1>Title</h1>
<p>Paragraph</p>
)
}
// ✅ Wrap in a fragment (no extra DOM node)
function Fixed() {
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
)
}JSX Expressions vs Statements
// ✅ Expressions are fine
const name = 'Alice'
const element = <h1>Hello, {name}!</h1>
// ✅ Function calls (which return a value) work too
const element = <p>{new Date().toLocaleDateString()}</p>
// ❌ Statements cannot appear inside {}
const element = (
<div>
{if (isLoggedIn) { return <UserPanel /> }} // SyntaxError
</div>
)JSX is Just JavaScript
Because JSX compiles to a value (a plain object), you can store it in a variable, put it in an array, pass it as a function argument, or return it from a function — just like any other JavaScript value:
// Store JSX in a variable
const greeting = <span>Good morning!</span>
// Build an array of elements
const items = ['Apple', 'Banana', 'Cherry'].map((fruit) => (
<li key={fruit}>{fruit}</li>
))
// Conditionally assign JSX
let icon
if (status === 'success') {
icon = <CheckIcon />
} else if (status === 'error') {
icon = <ErrorIcon />
}
// Use them in the render
function StatusBar({ status }) {
return (
<div className="status-bar">
{icon}
<span>{greeting}</span>
</div>
)
}Quick Reference
JSX is a syntax extension — it compiles to
React.createElement()(or the newjsx()function) at build timeJSX produces a plain JavaScript object (a React element), not real DOM
Every JSX expression must have one root element — use a Fragment
<></>to avoid extra DOM nodesJSX uses camelCase for attributes:
className,onClick,htmlForEmbed JavaScript expressions with curly braces
{}; statements are not allowed inlineAll tags must be closed:
<br />,<img />,<MyComponent />