JSX Syntax Rules
JSX is intentionally close to HTML, but it follows JavaScript rules.
Most JSX errors come from forgetting that you are writing JavaScript syntax, not an HTML file.
Return One Root Value
A component must return one value. That value can contain many children, but the top level needs a single parent.
// Invalid
function Profile() {
return (
<h1>Ada Lovelace</h1>
<p>Mathematician</p>
);
}Wrap siblings in an element:
function Profile() {
return (
<section>
<h1>Ada Lovelace</h1>
<p>Mathematician</p>
</section>
);
}Or use a fragment when you do not want an extra DOM node:
function Profile() {
return (
<>
<h1>Ada Lovelace</h1>
<p>Mathematician</p>
</>
);
}Close Every Tag
HTML sometimes lets you omit closing tags. JSX does not.
// Invalid JSX
<img src="/avatar.png">
<input type="text">
// Valid JSX
<img src="/avatar.png" alt="User avatar" />
<input type="text" />Self-closing tags are common for elements with no children and for component calls.
<Avatar user={user} />
<Divider />Use Parentheses for Multi-Line JSX
When JSX spans multiple lines, wrap it in parentheses.
function EmptyState() {
return (
<section className="empty-state">
<h2>No projects yet</h2>
<p>Create your first project to get started.</p>
</section>
);
}Without parentheses, automatic semicolon insertion can return undefined.
function BrokenEmptyState() {
return
<p>No projects yet</p>;
}The code above is interpreted like this:
function BrokenEmptyState() {
return;
}Use camelCase for Most DOM Props
JSX props use JavaScript property naming.
<button className="primary" onClick={handleSave}>
Save
</button>Common translations:
classbecomesclassNameforbecomeshtmlForonclickbecomesonClicktabindexbecomestabIndexreadonlybecomesreadOnly
ARIA and data attributes keep their dash-case names.
<button aria-label="Close dialog" data-testid="close-button">
X
</button>Put JavaScript Expressions in Curly Braces
Use quotes for plain strings.
<UserCard name="Grace" />Use braces for JavaScript values.
<UserCard name={currentUser.name} isAdmin={currentUser.role === "admin"} />Do not wrap booleans, numbers, arrays, or objects in quotes unless you really want strings.
// Passes the number 3
<Rating value={3} />
// Passes the string "3"
<Rating value="3" />Comments Have JSX Syntax
Inside JSX, comments must be JavaScript comments inside braces.
function Toolbar() {
return (
<div>
{/* This button is hidden until permissions are added. */}
<button disabled>Delete</button>
</div>
);
}HTML comments are not valid JSX.
// Invalid
<!-- comment -->Common Mistakes
- Returning JSX on the line after
returnwithout parentheses. - Using
classinstead ofclassName. - Forgetting to close
<img />,<input />, or a custom component. - Passing numbers or booleans as quoted strings.
- Placing
// commentdirectly inside JSX markup without wrapping it in{}.
Why do React components usually wrap multi-line JSX in parentheses after return?
Mini Challenge
Fix this component:
function SearchBox({ label, disabled }) {
return
<label for="search">
{label}
<input id="search" class="field" disabled="disabled">
</label>
}Your corrected version should return valid JSX, use the correct attribute names, and pass disabled as a boolean value.
Recap
JSX must return one root value, close every tag, use JavaScript-style prop names, and wrap expressions in curly braces. These rules feel strict at first, but they make components predictable JavaScript.