JSX and TSX: Syntax, Expressions, Attributes, and Composition
Master the core rules of JSX/TSX syntax — embedding expressions, JSX-specific attribute names, boolean attributes, fragments, comments, styling, and how these pieces combine to compose components.
JSX is the syntax you'll write in nearly every React file, so its rules need to become second nature. This lesson walks through embedding JavaScript expressions inside markup, the handful of attribute names and behaviors that differ from plain HTML, fragments and comments, and the basics of styling and composing components with JSX — setting up the deeper dives into conditional rendering, lists, and events that come in later modules.
JSX syntax and embedding expressions
JSX looks like HTML but is actually an expression-producing syntax extension to JavaScript/TypeScript — every JSX tag ultimately becomes a React.createElement(...) call (or, with the modern automatic JSX transform, a similar call imported automatically), meaning a JSX expression can be assigned to a variable, passed as an argument, or returned from a function just like any other value.
Curly braces { } are how you drop back into plain JavaScript/TypeScript inside JSX markup — anywhere you write {someExpression}, React evaluates that expression and inserts the result. This works for variables ({name}), function calls ({formatDate(date)}), arithmetic ({price * quantity}), ternaries, and any other single JavaScript *expression* — but not statements like if or for loops directly inside the braces, since JSX expects a value, not a block of statements.
{ } can appear both where a tag's children go (<p>{message}</p>) and inside an attribute's value (<img src={imageUrl} />) — in the attribute position you omit the surrounding quotes, since you're passing a real JavaScript value (which could be a string, number, object, or function) rather than a literal string.
Attributes: className, htmlFor, and camelCase props
JSX attributes mostly mirror HTML attributes but use camelCase for anything with a hyphen or multiple words in HTML (tabindex becomes tabIndex, onclick becomes onClick), because JSX attribute names become JavaScript object property names under the hood, and hyphens aren't valid in unquoted property names.
Two renames deserve special attention because they conflict with reserved JavaScript keywords: class becomes className (since class is a reserved word for JavaScript's class syntax), and for (used on <label> to associate it with an input) becomes htmlFor (since for is reserved for for-loops). Forgetting these and writing class= or for= in JSX doesn't error, but silently does nothing — React won't apply them as the styling/association attribute you meant.
Boolean HTML attributes (disabled, checked, required, readOnly) work by presence/absence in JSX by using an actual boolean value: <button disabled={true}> renders it disabled, <button disabled={false}> renders it enabled, and simply writing <button disabled> is shorthand for disabled={true}. This is more explicit and less error-prone than raw HTML, where the mere presence of the attribute name (regardless of its string value) makes it 'true'.
Fragments, nesting, and comments
Because a component can only return one root JSX element, React provides Fragments — <React.Fragment>...</React.Fragment> or its shorthand <>...</> — to group multiple sibling elements without adding an actual extra wrapper node (like a <div>) to the rendered DOM. This matters for layouts where an unwanted wrapper element would break CSS (e.g., a required <tr>/<td> structure in a table, or flex/grid child relationships).
JSX elements nest freely, exactly like HTML: any element can contain other elements as children, to any depth, and those children can themselves mix plain text, expressions in { }, and further nested components. Whitespace and indentation in your source don't usually change the rendered output significantly, but excessive nesting is a sign a piece of UI should be split into its own named component.
JSX has its own comment syntax because regular // and /* */ don't work inside the markup portion — inside JSX children you write {/* like this */}, a JavaScript block comment wrapped in an expression container so JSX treats it as an expression to evaluate and discard, rather than displaying it as text.
A preview of conditional rendering, lists, keys, and events
You'll cover these in full depth in later dedicated modules, but they're worth a first mention here since they're just more expressions inside { }. Conditional rendering commonly uses the ternary operator ({isLoggedIn ? <Dashboard /> : <LoginForm />}) or the && operator ({hasError && <ErrorBanner />}) to decide what to render based on a boolean.
Rendering a list of items commonly uses .map() inside { } to transform an array of data into an array of JSX elements: {items.map(item => <li key={item.id}>{item.name}</li>)}. Each element produced this way needs a key prop — a stable, unique string or number identifying that item — so React can efficiently track which list items changed, were added, or were removed between renders, instead of re-rendering the entire list from scratch.
Events are attached with camelCase handler props like onClick, onChange, and onSubmit, each set to a function (not a string, unlike HTML's inline onclick="...") that React calls when that interaction happens: <button onClick={() => setCount(count + 1)}>.
Styling and composition
CSS classes are applied via className, exactly like class in HTML, and typically reference styles defined in an imported .css file. Inline styles are set via the style attribute, but unlike HTML's style="color: red;" string, JSX's style takes a JavaScript object with camelCase property names and values that are usually strings (or numbers, which React treats as pixels for most properties): style={{ color: "red", fontSize: 16 }}. Note the double curly braces here — the outer pair is JSX's 'insert an expression' syntax, and the inner pair is the object literal itself.
Composition is simply using components inside other components' JSX, exactly the same way you use built-in HTML tags — <Header /> inside <App />'s return statement is composition, and it's how arbitrarily complex UIs are built from small, well-understood pieces, each with clear responsibility.
A simple component using an expression
{name} embeds the name prop's value directly into the rendered text — React converts strings and numbers to text nodes automatically. The function's inline parameter type { name: string } types the single props object this component receives; for anything beyond a one-off inline type, a separate type WelcomeProps = { name: string } declared above the component is the more common, more readable convention.
function Welcome({ name }: { name: string }) {
return <h1>Hello, {name}</h1>;
}Code Examples
{name} is a JavaScript expression embedded inside JSX text content — React evaluates the name variable and inserts its current value as text, right in the middle of the <h1> element's children. Expected result: Hello, Priya
This example combines a JSX comment, className, an inline style object (note the double braces), the htmlFor/id pairing for label association, a boolean disabled attribute driven by an expression, and a ternary for conditional text — all inside a <>...</> Fragment so two siblings (div and p) can be returned without an extra wrapper element. Expected result: When isFeatured is true: a gold-bordered card with an enabled input and the text 'Featured item' beneath it. When false: a gray-bordered card with a disabled input and 'Standard item' beneath it.
function Welcome({ name }: { name: string }) {
return <h1>Hello, {name}</h1>;
}
<Welcome name="Priya" />;
// Renders: <h1>Hello, Priya</h1>type CardProps = {
title: string;
isFeatured?: boolean;
};
function Card({ title, isFeatured = false }: CardProps) {
return (
<>
{/* Fragment avoids adding an extra wrapper div around these siblings */}
<div
className="card"
style={{ border: isFeatured ? "2px solid gold" : "1px solid #ccc" }}
>
<h3>{title}</h3>
<label htmlFor="note">Note</label>
<input id="note" type="text" disabled={!isFeatured} />
</div>
<p>{isFeatured ? "Featured item" : "Standard item"}</p>
</>
);
}Where curly braces switch you into JavaScript
Everywhere you see { } inside JSX, you have stepped out of markup and into a plain JavaScript/TypeScript expression that gets evaluated and its result inserted.
<div className={cardClass}> <-- {} in attribute position: real JS value, no quotes
<h2>{title}</h2> <-- {} in children position: inserts an expression's result
{/* a JSX comment */}
{items.map(i => <li key={i.id}>{i.name}</li>)} <-- {} can hold any expression, incl. .map()
</div>Important Rules
Use { } to embed any single JavaScript expression inside JSX — not statements like raw if or for.
Use className instead of class, and htmlFor instead of for — both are renamed to avoid colliding with reserved JavaScript keywords.
Boolean attributes take a real boolean expression (disabled={isDisabled}); bare disabled is shorthand for disabled={true}.
A component must return exactly one root element — use a Fragment (<>...</>) to group siblings without adding an extra DOM node.
Comment inside JSX markup with {/* ... */}, not // or bare /* */.
Any element produced from a .map() call needs a stable, unique key prop.
Where this shows up
These JSX rules are used in literally every component you will ever write in React — className/htmlFor renames, expression embedding with { }, Fragments to avoid layout-breaking wrapper divs, and boolean attributes driven by state are constant, everyday patterns, not edge cases. Getting them automatic now is what lets you focus on component design and data flow in later lessons instead of fighting syntax.
<div class="card" style="color: red;">Hi</div> <div className="card" style={{ color: "red" }}>Hi</div>
class is a reserved JavaScript keyword, so JSX requires className instead — using class doesn't throw an error but is silently ignored (no styling applied). Similarly, JSX's style attribute takes a JavaScript object with camelCase keys, not a CSS string, so a string value here is a type error in TSX and won't apply the intended styles.
[multiple-choice] Why does JSX use className instead of class? A. className is faster to type B. class is a reserved keyword in JavaScript (used for class declarations), so JSX renames the attribute to avoid the collision C. className applies multiple classes while class applies only one D. There is no difference; both work identically
B. class is a reserved keyword in JavaScript (used for class declarations), so JSX renames the attribute to avoid the collision — Since JSX attributes ultimately become JavaScript object properties/keywords are reserved, class can't be used directly — JSX renames it to className (and for to htmlFor for the same reason).
[predict-output] What does <button disabled={false}>Click</button> render as? A. A disabled button, because the disabled attribute is present B. An enabled, clickable button, because the boolean value is explicitly false C. A compile error D. A button with no text
B. An enabled, clickable button, because the boolean value is explicitly false — Unlike raw HTML (where any presence of the attribute name makes it truthy regardless of its string value), JSX respects the actual boolean expression — disabled={false} results in an enabled button.
[multiple-choice] Why would you wrap two sibling JSX elements in a Fragment (<>...</>) instead of a <div>? A. Fragments render faster in every case B. A component must return one root element, and a Fragment groups siblings without adding an actual extra wrapper node to the DOM C. Div elements are deprecated in JSX D. Fragments are required whenever using className
B. A component must return one root element, and a Fragment groups siblings without adding an actual extra wrapper node to the DOM — A Fragment satisfies JSX's single-root-element requirement while leaving no trace in the rendered DOM, which matters when an extra wrapper <div> would break CSS layout rules (like required table or flex/grid child structures).
Write a PriceTag component that takes amount: number and onSale?: boolean props. It should render the amount formatted as $amount, wrapped in a <span> whose className is "price sale" when onSale is true and just "price" otherwise, using a Fragment if you render any sibling text alongside it.
type PriceTagProps = { amount: number; onSale?: boolean; }; function PriceTag({ amount, onSale = false }: PriceTagProps) { return ( <span className={onSale ? "price sale" : "price"}> ${amount} </span> ); }
Build the className string with a ternary or template literal inside { }, and remember className, not class.