JSX Fragments and Children
Every JSX expression must return a single root element, but sometimes you don’t want that root to leave a trace in the DOM. React Fragments solve exactly this problem: they let a component return multiple elements side by side without wrapping them in an extra div or span. Closely related is the children prop, the special prop React gives every component for whatever is nested between its opening and closing tags. Together, Fragments and children are the foundation of how React components compose cleanly without polluting your rendered HTML.
Overview / How it works
JSX compiles down to calls like React.createElement(type, props, ...children). A JSX expression can only produce one value, so return <h2>...</h2><p>...</p> is invalid — there are two sibling elements with nothing to hold them. The traditional fix was to wrap siblings in a div, but that adds a real node to the DOM tree that often breaks CSS (a flex or grid layout expecting direct children), breaks valid HTML nesting (a div is not allowed directly inside a tr or dl), or simply adds meaningless markup.
Fragment is a special component that groups children without rendering anything of its own into the DOM. When React reconciles a Fragment, it treats its children as if they were direct children of the Fragment’s parent — no extra element appears in the actual HTML output, only the elements you intentionally rendered. This makes Fragments purely a JSX/authoring convenience, invisible to the browser and invisible to your styling.
The children prop works differently but complements Fragments perfectly. Whatever you nest between a component’s opening and closing tags is automatically packaged into a children prop and passed to that component. It’s how layout and container components (cards, modals, layout shells, list wrappers) accept arbitrary content without needing to know what that content is in advance. children can be a single element, a string, a number, an array of elements, null/undefined (renders nothing), or even a function (used for the “render props” pattern). Because JSX just compiles to createElement(Component, props, child1, child2, ...), the third-and-later arguments become props.children automatically — you never set it explicitly when writing JSX, only when calling createElement by hand.
Fragments and children are both about composition without extra DOM nodes: Fragments let a component’s own output be multiple elements, while children lets a component’s input be arbitrary nested content. Neither one changes React’s rendering model — state, re-renders, and reconciliation all work exactly the same whether or not a Fragment is involved, because a Fragment is not a real host element.
Syntax
// Full form
import { Fragment } from "react";
function Example() {
return (
<Fragment>
<h2>Title</h2>
<p>Text</p>
</Fragment>
);
}
// Shorthand form (most common)
function Example() {
return (
<>
<h2>Title</h2>
<p>Text</p>
</>
);
}
// children prop
function Wrapper({ children }) {
return <div className="wrapper">{children}</div>;
}
| Form | When to use |
|---|---|
<>...</> |
Default choice; groups elements with zero extra syntax. Cannot accept any props, including key. |
<Fragment>...</Fragment> |
Explicit form imported from react. Needed when you must pass a key, e.g. inside .map(). |
props.children |
The content nested between a custom component’s opening and closing tags, passed in automatically. |
Examples
Example 1: Avoiding an unnecessary wrapper div
function UserInfo({ name, email }) {
return (
<>
<h2>{name}</h2>
<p>{email}</p>
</>
);
}
export default UserInfo;
Renders: an h2 containing the name and a p containing the email, placed directly as siblings in the parent’s DOM — with no wrapping div in between. If UserInfo is rendered inside a flex container, the h2 and p become direct flex items, exactly as if you’d written them inline.
Without the Fragment, UserInfo would need a div to satisfy the “single root element” rule, and that div would show up in DevTools and could interfere with CSS selectors like .flex-container > * that target direct children.
Example 2: Keyed fragments inside a list
import { Fragment } from "react";
function GlossaryList({ terms }) {
return (
<dl>
{terms.map((term) => (
<Fragment key={term.id}>
<dt>{term.word}</dt>
<dd>{term.definition}</dd>
</Fragment>
))}
</dl>
);
}
export default GlossaryList;
Renders: a dl element containing one dt/dd pair per term, with valid HTML nesting (only dt and dd live directly inside dl, no stray div). Each pair is a separate Fragment, so React can track them individually across re-renders using term.id as the key.
This is the case where the shorthand <></> won’t work: JSX list items each need a unique key so React can match old and new items during reconciliation, and the shorthand syntax has no way to accept attributes. The explicit <Fragment key={...}> form is the only option here.
Example 3: Composition with the children prop
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
function App() {
return (
<Card title="Welcome">
<p>This content is passed as children.</p>
<button onClick={() => alert("Hi!")}>Click me</button>
</Card>
);
}
export default App;
Renders: a div.card containing an h3 reading “Welcome” and a div.card-body that itself contains the p and button that App nested inside <Card>. Clicking the button shows a browser alert reading “Hi!”.
Card never needed to know it would receive a paragraph and a button — it just renders whatever children it’s given, wrapped in its own title and styling. This is how reusable layout components (cards, modals, page shells, accordions) stay generic: the parent decides the content, the child component decides the surrounding structure.
How it works step by step
- On mount: React calls the component function, gets back a JSX tree. A Fragment node in that tree is not turned into a DOM element — React “flattens” it, inserting its children directly into the parent’s DOM position. For
children, whatever elements the caller nested between the tags were already resolved into a prop value before the child component ever ran, so the child simply places{children}wherever it wants in its own returned JSX. - On a state update: React re-runs the component function and produces a new tree, then diffs it against the previous one (reconciliation). Because a Fragment isn’t a host node, React compares its children as if they were siblings at that position — keys on those children (or on the Fragment itself, in a list) are what let React match old elements to new ones instead of re-mounting everything.
- On unmount: children rendered via a Fragment or via
props.childrenare unmounted the same way any other element is — effects clean up, DOM nodes are removed. There’s nothing special to clean up for the Fragment itself since it never had a DOM node to begin with.
Common Mistakes
Mistake 1: Returning adjacent elements with no wrapper at all
function Bad() {
return (
<h2>Title</h2>
<p>Text</p>
);
}
This is a syntax error: “Adjacent JSX elements must be wrapped in an enclosing tag.” A JSX expression can only evaluate to one root node.
function Good() {
return (
<>
<h2>Title</h2>
<p>Text</p>
</>
);
}
Mistake 2: Trying to key the shorthand fragment
function List({ items }) {
return (
<ul>
{items.map((item) => (
<>
<li>{item.label}</li>
</>
))}
</ul>
);
}
The <></> shorthand cannot accept any props, so there’s no way to give it a key. React will log “Each child in a list should have a unique key prop” and fall back to less efficient, error-prone index-based matching during reconciliation. Import and use the explicit Fragment component instead:
import { Fragment } from "react";
function List({ items }) {
return (
<ul>
{items.map((item) => (
<Fragment key={item.id}>
<li>{item.label}</li>
</Fragment>
))}
</ul>
);
}
Mistake 3: Forgetting to render children
function Wrapper({ children }) {
return <div className="wrapper"></div>;
}
The children prop was received but never used in the returned JSX, so anything nested inside <Wrapper>...</Wrapper> is silently dropped — a common source of “why isn’t my content showing up” bugs.
function Wrapper({ children }) {
return <div className="wrapper">{children}</div>;
}
Best Practices
- Default to the
<>...</>shorthand; only import and use the explicitFragmentcomponent when you need akey, most commonly inside.map(). - Use a Fragment instead of a wrapper
divwhenever the extra element would break CSS (grid/flex direct-child selectors) or invalid HTML nesting (rows in atable, items in adl,options in aselect). - Design container/layout components (cards, modals, panels) around
childrenrather than a fixed set of named props, so callers can pass arbitrary nested content. - Give
childrena default only when it makes sense to render something in its absence — otherwise an empty/undefinedchildrensimply renders nothing, which is usually fine. - Don’t reach for a Fragment out of habit when a real wrapper element (with a
classNamefor styling) is actually what you need — Fragments are for when you specifically want no extra DOM node. - Remember a component can accept both
childrenand other named props at the same time, e.g.<Card title="Welcome">...</Card>.
Practice Exercises
- Write a
Statscomponent that returns three sibling<dt>/<dd>pairs (label/value) using the Fragment shorthand, meant to be rendered directly inside a parent<dl>. - Given an array of
{ id, question, answer }objects, render a list of keyed Fragments where each Fragment contains an<h3>for the question and a<p>for the answer. Explain in a comment why the shorthand fragment can’t be used here. - Build a
Modalcomponent that renders a fixed header and footer, withchildrenrendered in between as the body. Use it twice with different content to confirm it’s fully reusable.
Summary
- JSX requires a single root element; Fragments let you group multiple elements without adding a real DOM node.
- Use the
<>...</>shorthand by default; switch to the explicit<Fragment>import when you need to pass akey, such as inside.map(). - The
childrenprop holds whatever is nested between a component’s opening and closing tags, enabling flexible composition. - Fragments and
childrendon’t change React’s rendering or reconciliation model — a Fragment simply isn’t turned into a host DOM element. - Forgetting to render
{children}, or trying to key a shorthand Fragment, are the two most common mistakes to watch for.
