JSX is the syntax that lets you write the structure of a component in a form that looks like HTML. It is what makes React code readable, and it is the first thing that feels strange. This lesson gives you the rules in one place, so the error messages stop being mysterious. Every example runs in the editor: change it, break it on purpose and read what the page says.
JSX is JavaScript, not HTML
A tag in JSX is a shorthand for a function call. When the build tool compiles this line:
const element = <h1 className="title">Hello, {name}!</h1>;it produces ordinary JavaScript (React 19's automatic runtime, shown here as compiled by Babel):
const element = _jsxs("h1", {
className: "title",
children: ["Hello, ", name, "!"]
});The call creates a plain JavaScript object, a React element, describing an h1 with a class and three children. React compares such objects to work out what to change in the page. You never need to write these calls yourself. Understanding that JSX is JavaScript explains all the rules below: they exist because the code must still be valid JavaScript underneath.
Rule 1: return one root element
A function can return only one value, so a component must return one element. This fails:
function Page() {
return (
<h1>Title</h1>
<p>Text</p>
);
}Wrap the pair in a parent element, or in a fragment, an empty tag that groups children without adding anything to the page:
function App() {
return (
<>
<h1>Title</h1>
<p>Text</p>
</>
);
}
export default App;Title
TextPrefer a fragment when you do not need a wrapper div; it keeps the HTML clean and avoids breaking layouts such as lists and tables. When you return JSX over several lines, wrap it in round brackets ( ... ) so that JavaScript does not stop after return and treat the next line as separate.
Rule 2: close every tag
HTML lets you leave some tags open (<br>, <img>, <input>). JSX does not: every element must be closed, either with a closing tag or, for elements without children, with a self-closing slash.
const dot = "data:image/svg+xml," + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><circle cx="20" cy="20" r="20" fill="#bba8ff"/></svg>'
);
function App() {
return (
<div>
<label>Name <input type="text" placeholder="Your name" /></label>
<br />
<p>Line one<br />Line two</p>
<img src={dot} alt="A purple dot" width="40" height="40" />
<hr />
</div>
);
}
export default App;Name
Line one
Line twoYour own components follow the same rule: <Header /> for one without children.
Rule 3: attribute names are camelCase, with a few renames
JSX attributes become JavaScript object properties, so they use camelCase, and words that clash with JavaScript are renamed:
| HTML | JSX |
|---|---|
class |
className |
for (on a label) |
htmlFor |
tabindex |
tabIndex |
onclick |
onClick |
maxlength |
maxLength |
readonly |
readOnly |
autofocus |
autoFocus |
charset |
charSet |
The exceptions are aria-* and data-* attributes, which keep their hyphens: aria-label="Close", data-id="7". If you use the wrong name, React's console warning tells you the right one.
Rule 4: curly braces switch back to JavaScript
Inside JSX, { ... } opens a window into JavaScript. You can put any expression there, in two places: as the content of an element, or as an attribute value.
function App() {
const user = { name: "Asha", visits: 3 };
const avatar = "data:image/svg+xml," + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><circle cx="20" cy="20" r="20" fill="#86dbb4"/></svg>'
);
const label = "Profile photo of " + user.name;
return (
<div>
<h1>Hello, {user.name}!</h1>
<p>You have visited {user.visits} times, which is {user.visits * 7 % 5} mod 5.</p>
<p>Today is a {new Date(2026, 9, 24).toLocaleDateString("en-GB", { weekday: "long" })}.</p>
<img src={avatar} alt={label} width="40" height="40" />
</div>
);
}
export default App;Hello, Asha!
You have visited 3 times, which is 1 mod 5.
Today is a Saturday.Points to remember:
- An expression produces a value: variables, arithmetic, function calls, ternaries,
array.map(...). A statement does not:if,for,const x = 1. Statements cannot go inside braces. Compute the value above thereturn, or usecondition ? a : band&&inside. - For attributes, use braces instead of quotes:
src={avatar}. Writesrc="avatar"and you get the literal textavatar. Never both:src="{avatar}"is the text{avatar}. - Strings, numbers and elements render as content.
true,false,nullandundefinedrender nothing. Plain objects cannot be rendered and cause the error "Objects are not valid as a React child"; show a property instead. - Values are escaped, so putting user text in braces is safe, as the next example shows.
Text is escaped automatically
If a value contains HTML, React shows it as text rather than running it. This protects you from cross-site scripting (XSS), an attack where someone enters <script> or <img onerror=...> in a form.
function App() {
const comment = '<img src=x onerror="alert(1)"> <b>bold?</b>';
return <p>{comment}</p>;
}
export default App;<img src=x onerror="alert(1)"> <b>bold?</b>The tags appeared as plain characters, and nothing ran. If you really need to insert trusted HTML (for example, from your own editor), there is an escape hatch whose name is a warning: dangerouslySetInnerHTML={{ __html: html }}. Never use it with text from users.
Inline styles are objects
The style attribute takes a JavaScript object, not a string. That is why you see double braces: the outer pair opens JavaScript, the inner pair is the object. Property names are camelCase, and numbers get px added for sizes.
function App() {
const box = {
backgroundColor: "#ffd45c",
padding: 12, // 12px
borderRadius: 8,
fontSize: "1.1rem",
};
return (
<div>
<p style={box}>A styled paragraph</p>
<p style={{ color: "crimson", fontWeight: "bold" }}>Inline object</p>
</div>
);
}
export default App;A styled paragraph
Inline objectInline styles are fine for values that change with data, such as a progress bar's width. For everything else, use CSS classes with className, covered in Styling React apps.
Comments, conditionals and whitespace
Comments inside JSX need braces: {/* a comment */}. JSX also collapses whitespace: line breaks and indentation between tags are removed, and spaces inside text are trimmed at line edges. To keep a space between two inline pieces that sit on separate lines, add {" "}.
function App() {
return (
<p>
{/* this comment is not rendered */}
First line,
continues here.{" "}
<strong>bold</strong>
</p>
);
}
export default App;First line, continues here. boldComponents are tags with capital letters
JSX treats a lower-case tag (<div>, <button>) as an HTML element and a capitalised tag (<Greeting />) as a component you defined. That is why component names must start with a capital letter. The next lesson builds your first component.
Reading error messages
| Message | Likely cause |
|---|---|
Adjacent JSX elements must be wrapped in an enclosing tag |
two root elements; add a fragment |
Unterminated JSX contents or Expected corresponding JSX closing tag |
an element was not closed |
Invalid DOM property class. Did you mean className? (console warning) |
class instead of className |
Objects are not valid as a React child |
you put an object or a Date directly in braces |
x is not defined |
a variable used in braces that was never declared or imported |
Unexpected token |
a statement such as if inside braces, or a missing bracket |
Common mistakes
- Writing
classandforinstead ofclassNameandhtmlFor. - Returning multiple root elements without a fragment.
- Forgetting to close
<img>,<input>or<br>. - Putting quotes around braces:
value="{count}". - Writing
style="color: red"; use an object. - Putting
iforforstatements inside braces instead of using expressions (? :,&&,map). - Lower-case component names such as
<greeting />, which React treats as an unknown HTML tag.
Exercise
- In the first editor, add a third paragraph that shows
2 + 3computed inside braces. - Break the second editor on purpose: remove the
/from<br />. Read the message, then fix it. - In the style example, add
textAlign: "center"tobox, and make the second paragraph's colour come from aconst color = "teal"variable. - Put
<b>hello</b>in a variable and render it in braces. What do you see, and why is that the safe behaviour?
Key points
- JSX looks like HTML but is JavaScript: each tag becomes a function call that creates a React element.
- A component returns one root element; use a fragment (<>...</>) to group elements without an extra wrapper.
- Close every tag, use className instead of class, and write attributes in camelCase (onClick, tabIndex, htmlFor).
- Curly braces put any JavaScript expression into JSX, as text or as an attribute value; style takes an object, not a string.