Key Takeaways
- JSX is optional syntax for describing React element trees, not a separate way to define components.
- Your tooling transforms JSX into JavaScript. The automatic runtime doesn't require a React import just for JSX.
- Curly braces embed JavaScript expressions; fragments group siblings without adding a DOM wrapper.
- React escapes interpolated text, but raw HTML rendering requires a separate trust and sanitization boundary.
React JSX is an optional JavaScript syntax extension that lets you describe a user interface with HTML-like tags. It's usually easier to read than equivalent element-creation calls, especially when your interface contains nested elements and components. JSX isn't HTML, and browsers don't natively execute it.
What Is JSX in React?
Think of JSX as notation for an element tree: a description of what React should render. JSX itself doesn't define a component. A component is a function or class that produces that description. JSX works with both function and class components; the examples here use functions.
This compact component embeds a JavaScript variable in its heading:
export default function Greeting() {
const name = 'Sam';
return <h1 className="title">Hello {name}!</h1>;
}Greeting is the component; <Greeting /> is an element describing a use of that component. For simple tag names, capitalization matters: <Button /> refers to a component variable, while <button /> names a built-in browser element. See our guide to React components for the next step.
JSX isn't part of the ECMAScript language specification. Its standalone syntax specification leaves its meaning to the consuming transform, so other libraries can use JSX too. This tutorial teaches React's interpretation.
The Benefits of Using JSX
Compare these two descriptions of the same heading. First, JSX:
// JSX expression fragment.
const element = (
<h1 className="greeting">Hello, world!</h1>
);Then the equivalent expression using React.createElement, without JSX:
import * as React from 'react';
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);Both describe the same UI. JSX makes opening and closing elements easier to match, and that advantage grows with nesting. You can still use React.createElement(...) when JSX isn't available or doesn't suit your project.
Keep the tooling benefits separate from the syntax. A JSX parser can catch malformed markup, such as unclosed tags; TypeScript can separately check component prop types. Neither readability nor successful JSX compilation guarantees faster application code.
How JSX Is Transformed into JavaScript
A configured compiler, such as Babel or TypeScript, transforms JSX before the application executes it. Your framework or build tool may already provide that configuration. If you need a working project first, follow the React environment setup tutorial.
JSX source
|
v
Configured compiler / transform
|
+-- Classic: React.createElement(...)
|
+-- Automatic: imported JSX runtime functions
|
v
JavaScript consumed by the applicationThe classic React transform emits calls to React.createElement, so that identifier must be available. The automatic transform inserts imports from react/jsx-runtime; you don't need an otherwise unused React import just to write JSX. You still import any React APIs you actually reference.
For example, representative automatic-transform output for the heading above looks like this. This is explanatory compiler output, not code you should maintain by hand:
// Representative automatic-transform output.
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', {
className: 'greeting',
children: 'Hello, world!'
});The exact emitted code depends on the compiler and its configuration. Consult Babel's JSX transform documentation when inspecting a Babel project. In TypeScript, JSX files use .tsx, and the jsx compiler option selects the output mode; react-jsx selects the automatic runtime.
JSX vs HTML: The Syntax Differences That Matter
JSX looks familiar, but React DOM props aren't simply copied HTML attribute names. Props are the values you pass to an element or component. React's common DOM props reference documents their names and behavior.
| Purpose | HTML | React JSX |
|---|---|---|
| CSS class | class="title" | className="title" |
| Label association | for="email" | htmlFor="email" |
| Tab order | tabindex="0" | tabIndex={0} |
| Inline styles | style="font-size: 20px" | style={{ fontSize: 20 }} |
| Click handler | onclick="handleClick()" | onClick={handleClick} |
Learn className as React DOM's prop name, corresponding to the DOM property. Don't treat it as proof that JavaScript object keys cannot be named class. Many React DOM props use camelCase, but aria-* and data-* attributes keep their hyphenated names.
Close every JSX tag explicitly. For example, write <input />, not <input>, and close paired tags such as <p>Text</p>. A boolean prop without a value means true: <input disabled /> is equivalent to <input disabled={true} />. Pass disabled={false} to leave it enabled, not the string "false".
Pass an Event Handler, Not Its Result
React event props receive functions. This component logs a message when its button is clicked:
export default function SaveButton() {
function handleClick() {
console.log('Save requested');
}
return (
<button type="button" onClick={handleClick}>
Save
</button>
);
}onClick={handleClick} passes the function. Writing onClick={handleClick()} would call it during rendering instead. React's event handling guide explains this distinction.
Using JavaScript Expressions with JSX
Braces let you embed an expression: JavaScript that produces a value. Variables, function calls, arithmetic such as {count + 1}, and conditional expressions all fit. Statements such as if and for don't go directly inside JSX braces; put them before the returned markup or use an expression such as a ternary or map().
Use the following as App.jsx in an existing React project configured with the automatic transform. It combines the original heading-and-paragraph example with a fragment, a condition, and a list:
const topics = [
{ id: 'expressions', title: 'JavaScript expressions' },
{ id: 'fragments', title: 'Fragments' }
];
export default function App() {
const name = 'Sam';
return (
<>
<h1 className="title">Hello {name}!</h1>
<p>Check out this paragraph.</p>
{topics.length > 0 ? (
<ul>
{topics.map((topic) => (
<li key={topic.id}>{topic.title}</li>
))}
</ul>
) : (
<p>No topics yet.</p>
)}
</>
);
}Return Multiple Elements with a Fragment
A return statement returns one value. To return sibling JSX elements, group them in a fragment, use a real parent element, or return an array. The <>...</> fragment above adds no wrapper node to the DOM. Use a real element when you need a semantic container or a layout and styling target.
If each list item needs several sibling elements, use an imported Fragment with a key. The shorthand fragment syntax cannot take a key; the Fragment reference shows the explicit form.
Conditions and Lists Use Ordinary JavaScript
The ternary expression above selects either the list or the empty-state paragraph. For an optional section, {topics.length > 0 && <p>Topics available</p>} also works. Avoid {topics.length && ...}: when the length is zero, React renders the number 0.
map() produces an array of elements. Give each generated sibling a stable key that's unique within that list; a persistent data ID is a good choice. Don't generate keys during rendering, and avoid array indexes for lists that can be reordered, inserted into, or deleted from.
key is used by React and isn't passed to a component as an ordinary prop. If a child also needs the identifier, pass it separately, for example as topicId. Continue with React props and state to understand how component inputs drive these expressions.
Styling with JSX
JSX doesn't require inline styling. Use className with stylesheets for reusable rules, and reserve the style prop for values that are naturally calculated in JavaScript.
export default function App() {
const titleText = {
fontSize: 100,
lineHeight: 1.2,
color: '#333'
};
return (
<>
<h1 className="title" style={titleText}>Hello React!</h1>
<p>Check out this paragraph.</p>
</>
);
}style receives an object, not a CSS string, and property names are usually camel-cased. Here, fontSize: 100 becomes 100px, while lineHeight: 1.2 remains unitless. Numeric values don't always mean pixels; use a string such as '2rem' when you want an explicit unit.
style={{ fontSize: 100 }} is the same object written inline: the outer braces enter JavaScript, and the inner braces define the object. Keep responsive rules and states such as :hover in CSS, rather than trying to encode stylesheet selectors in a style object.
Escaped Text and the Unsafe HTML Boundary
React renders interpolated strings as text instead of interpreting them as HTML. Ordinary text and string-attribute interpolation is escaped at the HTML boundary; it isn't a mechanism for sanitizing arbitrary HTML.
export default function HtmlBoundaryDemo() {
const suppliedText = '<strong>Sam</strong>';
// A fixed, application-owned HTML string, not user input.
const trustedMarkup = { __html: '<strong>Editorial notice</strong>' };
return (
<>
<p title={suppliedText}>{suppliedText}</p>
<div dangerouslySetInnerHTML={trustedMarkup} />
</>
);
}The paragraph displays the literal text <strong>Sam</strong>. The second element interprets its string as HTML and displays a bold notice. dangerouslySetInnerHTML bypasses the normal text-rendering protection and can introduce cross-site scripting (XSS) if its HTML isn't trusted.
Common Mistakes
- Treating braces as a template language:
title={name}reads a variable;title="{name}"passes a literal string. - Adding a div only to satisfy JSX: choose a fragment when you don't need a DOM container.
- Ignoring key warnings: put the key on the element returned directly from the list mapping.
- Assuming compilation proves correctness: use type checking for prop contracts and tests for behavior. Start with our guide to React component testing.
Choose JSX when you want an element tree that's easy to scan and maintain. Learn the element-creation equivalent to understand what JSX describes, then let your configured tooling handle the transformation.
FAQ
What is JSX in React?
JSX is an optional JavaScript syntax extension for describing element trees with HTML-like tags. It is not HTML or part of the ECMAScript specification, and other libraries can use it through their own transforms.
Is JSX required to use React?
No. You can create React elements with createElement instead of JSX. JSX is generally easier to read for nested UI descriptions, but both approaches describe elements for React to render.
Do I need to import React when using JSX?
Not when your project uses the automatic JSX transform, which inserts the runtime imports. The classic React.createElement transform requires React to be in scope, and explicitly referenced React APIs still need imports. Browsers don't execute JSX source directly; configured tooling must transform it first.
How do you return multiple elements in JSX?
Group sibling elements in a fragment, use a real parent element, or return an array of elements with appropriate keys. A fragment adds no DOM wrapper; a real element is useful when you need a semantic container or a layout target.
How do I handle CSS with JSX?
Use className to apply CSS classes, or pass a JavaScript object to the style prop for inline styles. Style property names are usually camel-cased, and numeric values follow React's CSS rules rather than always representing pixels.
Further Reading
- Writing Markup with JSX — tag syntax, grouping, and HTML differences.
- JavaScript in JSX with Curly Braces — expressions, attributes, and object syntax.
- React createElement reference — the element-creation alternative to JSX.
- Babel JSX transform reference — classic and automatic runtime output.
- TypeScript JSX handbook — TSX files, output modes, and type checking.
- Conditional Rendering — branching, ternaries, and logical operators.
- Rendering Lists — mapping data and choosing stable keys.
- React DOM common components reference — DOM props, styles, and raw HTML precautions.
