<Fragment>
Groups a list of children under one JSX element without adding a wrapper node to the DOM.
<Fragment key={yourKey}>
<OneChild />
<AnotherChild />
</Fragment>
// shorthand (no key, no ref):
<>
<OneChild />
<AnotherChild />
</>- Added in React 16.2.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$children | ReactNode | The elements to group. They are emitted into the parent as if the grouping element were not there. |
$keyoptional | string | number | Identity for the group when it is rendered inside a list. Only available with the explicit <Fragment> form — the <>...</> shorthand cannot take one. |
$refoptional | Ref<FragmentInstance> | Canary only. A ref object or callback; React supplies a FragmentInstance that exposes methods for interacting with the DOM nodes the group wraps. Requires the explicit <Fragment> form. |
Return value
ReactNodeIts children, placed directly into the parent's output with no wrapper element of its own.
Changelog
- PHP 16.2.0Introduced along with the <>...</> shorthand syntax.
- PHP canaryA ref prop is supported in Canary builds, giving a FragmentInstance for the wrapped DOM nodes. Not available in stable releases.
A component may only return one element. When the markup you actually want is two or more siblings — a <dt> and a <dd>, several <td> cells, a heading followed by a paragraph — wrapping them in a <div> would put a node into the DOM that CSS grid, flexbox, tables and definition lists all care about. Grouping them instead emits nothing.
When to reach for it
- Returning siblings from a component whose parent is a
<table>,<dl>,<select>or a flex/grid container, where an extra wrapper would break layout or HTML validity. - Emitting pairs of nodes per item in a
map()— this is the case that needs the explicit form, because the group itself is the list item and needs akey.
What trips people up
The shorthand <>...</> accepts no attributes at all. Writing <> key={id} is a syntax error, so a keyed group must import the named export. Any prop other than key (and ref in Canary) is rejected with a development warning — you cannot pass className or event handlers to something that renders no element.
State survives the switch between <><Child /></>, [<Child />] and a bare <Child />, but only one level deep. Going from <><><Child /></></> to <Child /> remounts and resets it.
Example
When to use it
- Return a <dt>/<dd> pair per entry from a glossary row component.
- Emit several <td> cells from one component without breaking the surrounding <tr>.
- Group a label and its input inside a CSS grid without adding a cell-consuming <div>.
- Return a heading plus body from a component whose parent uses flexbox gap.
Frequently asked questions
Why can't I put a key on <>...</>?
The shorthand syntax has no attribute slot at all, so React provides no way to attach one. Import the named export and write <Fragment key={id}>...</Fragment> instead; that is the only form that accepts a key.
Does this add anything to the DOM?
No — its children are placed directly into the parent node, so the rendered HTML is identical to writing the siblings inline. That is the whole point: table, flex and grid layouts see the children as direct descendants.
Can I add a className or onClick to it?
No. It renders no element, so there is nothing for a class or handler to attach to, and React logs a development warning for any prop other than key (or ref in Canary builds). Use a real element when you need styling or events.
More examples
Sibling table cells from one component
Each <tr> contains three <td> elements and nothing else. Because FileCells groups its output rather than wrapping it, the browser's table layout still applies — with a <div> wrapper, HTML parsing rules would hoist the cells out of the row and the columns would collapse.
import { Fragment } from "react";
const rows = [
{ id: 1, name: "alpha", size: 120, owner: "ada" },
{ id: 2, name: "beta", size: 340, owner: "grace" },
];
// Returns three <td> siblings. A <div> here would be invalid inside <tr>.
function FileCells({ file }) {
return (
<>
<td>{file.name}</td>
<td>{file.size}</td>
<td>{file.owner}</td>
</>
);
}
export default function FileTable() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Owner</th>
</tr>
</thead>
<tbody>
{rows.map((file) => (
<tr key={file.id}>
<FileCells file={file} />
</tr>
))}
</tbody>
</table>
);
}Shorthand versus the explicit form
Keyed renders `react, jsx, dom` inside a single paragraph, with each tag and its trailing separator treated as one keyed list item. The commented-out Broken shows why: the shorthand has no place to put the key, so any list of grouped pairs must import the named export.
import { Fragment } from "react";
const tags = ["react", "jsx", "dom"];
function Broken() {
// Will not compile: <> cannot carry attributes.
// return tags.map((tag) => <key={tag}><code>{tag}</code>, </>);
return null;
}
function Keyed() {
return (
<p>
{tags.map((tag, i) => (
<Fragment key={tag}>
<code>{tag}</code>
{i < tags.length - 1 ? ", " : null}
</Fragment>
))}
</p>
);
}
export default function TagList() {
return (
<>
<h3>Tags</h3>
<Keyed />
</>
);
}State preservation across a grouping change
Typing in the input and pressing the button keeps the typed text. React treats a single-level group and a bare child as the same position in the tree, so Field is not remounted. Add a second level of nesting on one branch and the same toggle would clear the input instead.
import { useState } from "react";
function Field() {
const [value, setValue] = useState("");
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="type, then toggle"
/>
);
}
export default function ToggleWrapper() {
const [wrapped, setWrapped] = useState(true);
return (
<div>
<button onClick={() => setWrapped((w) => !w)}>
{wrapped ? "Unwrap" : "Wrap"}
</button>
<p>Currently {wrapped ? "grouped" : "bare"} — the text below survives either way.</p>
{wrapped ? (
<>
<Field />
</>
) : (
<Field />
)}
</div>
);
}
Discussion