JSX and TSX
Write declarative UI using JavaScript expressions that look like HTML — with full TypeScript support.
Recipe
Quick-reference recipe card — copy-paste ready.
// JSX expression
const element = <h1 className="title">Hello, {name}</h1>
// Fragment (no extra DOM node)
const list = (
<>
<dt>Term</dt>
<dd>Definition</dd>
</>
)
// Expression slot — any valid JS expression works inside { }
<p>{isLoggedIn ? `Welcome, ${user.name}` : "Please sign in"}</p>
// Spread props
<Button {...props} />When to reach for this: Every React component returns JSX. Understanding the rules saves hours of debugging.
Working Example
"use client";
import { useState } from "react";
interface UserCardProps {
name: string;
role: "admin" | "editor" | "viewer";
avatarUrl?: string;
}
export function UserCard({ name, role, avatarUrl }: UserCardProps) {
const [expanded, setExpanded] = useState(false);
const roleBadge: Record<string, string> = {
admin: "bg-red-100 text-red-800",
editor: "bg-blue-100 text-blue-800",
viewer: "bg-gray-100 text-gray-800",
};
return (
<article className="rounded-lg border p-4">
<div className="flex items-center gap-3">
{avatarUrl ? (
<img
src={avatarUrl}
alt={`${name}'s avatar`}
className="h-10 w-10 rounded-full"
/>
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-gray-200">
{name[0]}
</div>
)}
<div>
<h2 className="font-semibold">{name}</h2>
<span className={`rounded px-2 py-0.5 text-xs ${roleBadge[role]}`}>
{role}
</span>
</div>
</div>
{expanded && (
<p className="mt-3 text-sm text-gray-600">
{name} has <strong>{role}</strong> permissions in this workspace.
</p>
)}
<button
onClick={() => setExpanded(prev => !prev)}
className="mt-2 text-sm text-blue-600 underline"
>
{expanded ? "Show less" : "Show more"}
</button>
</article>
);
}What this demonstrates:
- Conditional rendering with ternary expressions inside JSX
- Dynamic
classNameusing template literals - Expression slots for computed values
- TypeScript interface for component props
Deep Dive
How It Works
- JSX is syntactic sugar —
<div className="x">compiles toReact.createElement("div", { className: "x" })(or the modern JSX transform which importsjsxfromreact/jsx-runtimeautomatically) - Every JSX expression must return a single root element — use
<>...</>(Fragment) when you don't want an extra wrapper in the DOM - Curly braces
{ }create an expression slot — anything that produces a value works: variables, ternaries, function calls, array methods - TSX is identical to JSX but lives in
.tsxfiles and supports TypeScript type-checking for props, expressions, and event handlers
JSX Rules at a Glance
| Rule | Correct | Wrong |
|---|---|---|
HTML class | className | class |
HTML for | htmlFor | for |
| Self-closing tags | <img /> | <img> |
| Style prop | style={{ color: "red" }} | style="color:red" |
| Boolean attributes | <input disabled /> | <input disabled="true"> |
| Data attributes | data-testid="x" | Works as-is |
Fragments
// Named fragment (when you need a key)
import { Fragment } from "react";
{items.map(item => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</Fragment>
))}
// Short syntax (no key needed)
<>
<Header />
<Main />
<Footer />
</>TypeScript Notes
// React.ReactNode — the broadest return type
function Wrapper({ children }: { children: React.ReactNode }) {
return <section>{children}</section>;
}
// React.JSX.Element — a single JSX element
function Badge(): React.JSX.Element {
return <span>OK</span>;
}
// Generic components
function List<T>({ items, render }: { items: T[]; render: (item: T) => React.ReactNode }) {
return <ul>{items.map((item, i) => <li key={i}>{render(item)}</li>)}</ul>;
}Gotchas
-
classvsclassName— Usingclassin JSX compiles but triggers a console warning and won't apply styles correctly in some environments. Fix: Always useclassName. -
Adjacent elements without a wrapper — Returning
<h1/><p/>without a parent element is a syntax error. Fix: Wrap in a Fragment<>...</>or a<div>. -
Rendering objects —
{user}throws "Objects are not valid as a React child" ifuseris an object. Fix: Render a specific property like{user.name}. -
Whitespace collapsing — JSX collapses whitespace more aggressively than HTML. Two inline elements like
<span>A</span><span>B</span>will have no space between them. Fix: Add{" "}between them or wrap with CSS gap. -
Comments in JSX — HTML comments
<!-- -->don't work. Fix: Use{/* comment */}inside JSX.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Template literals (tagged) | Building email HTML or static strings server-side | You need interactivity or React features |
React.createElement | Programmatically building elements in a loop without JSX | Readability matters (JSX is almost always clearer) |
| MDX | Writing content-heavy pages that mix Markdown and components | Building interactive app UI |
FAQs
What is the difference between JSX and TSX?
TSX is JSX inside .tsx files with TypeScript type-checking enabled. The syntax is identical, but TSX validates prop types, expression types, and event handler types at compile time.
Why do I have to use className instead of class?
class is a reserved keyword in JavaScript. JSX compiles to JavaScript function calls, so React uses className to avoid the conflict. Using class triggers a console warning.
What can go inside curly braces in JSX?
Any valid JavaScript expression — variables, ternaries, function calls, template literals, and array methods like .map(). Statements like if/else or for loops cannot go directly inside {}.
When should I use a Fragment instead of a div?
Use <>...</> when you need to return multiple sibling elements without adding an extra DOM node. Use <Fragment key={...}> when mapping a list that needs keys on the wrapper.
How does the style prop work in JSX?
The style prop accepts a JavaScript object with camelCase properties, not a CSS string:
<div style={{ backgroundColor: "red", fontSize: 16 }} />Can I use if/else inside JSX?
Not directly. Use a ternary expression {condition ? <A /> : <B />} or extract the logic into a variable before the return statement.
What does the spread operator do on a JSX element?
<Button {...props} /> passes all properties of the props object as individual attributes to the component. This is useful for forwarding props or extending native HTML elements.
How do I write comments inside JSX?
Use {/* comment */} inside JSX. HTML-style <!-- --> comments do not work and will cause a syntax error.
What is React.ReactNode vs React.JSX.Element?
React.ReactNodeis the broadest type — includes JSX, strings, numbers,null,undefined, and arraysReact.JSX.Elementis a single JSX element only, no strings or null- Use
ReactNodeforchildrenprops andJSX.Elementfor function return types that always return JSX
Why does JSX require a single root element?
JSX expressions compile to a single function call like React.createElement(...). Multiple adjacent elements would be multiple return values, which JavaScript doesn't support. Wrap siblings in a Fragment or a container element.
How do I render raw HTML strings in JSX?
Use dangerouslySetInnerHTML={{ __html: htmlString }}. Only use this with sanitized, trusted content — it bypasses React's XSS protections.
What happens when I render false, null, or undefined in JSX?
They render nothing. This is why {condition && <Component />} works — when condition is false, false renders as empty. However, 0 renders as the text "0".
Related
- Components — building blocks that return JSX
- Conditional Rendering — patterns for showing/hiding JSX
- Lists and Keys — rendering arrays of JSX elements