<Fragment>

Groups a list of children under one JSX element without adding a wrapper node to the DOM.

Signature
<Fragment key={yourKey}>
  <OneChild />
  <AnotherChild />
</Fragment>

// shorthand (no key, no ref):
<>
  <OneChild />
  <AnotherChild />
</>
  • Added in React 16.2.0

Parameters

ParameterTypeDescription
$childrenReactNodeThe elements to group. They are emitted into the parent as if the grouping element were not there.
$keyoptionalstring | numberIdentity for the group when it is rendered inside a list. Only available with the explicit <Fragment> form — the <>...</> shorthand cannot take one.
$refoptionalRef<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 a key.

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

Try it yourself
Loading editor…
Loading React…

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.

Example · jsx
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.

Example · jsx
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.

Example · jsx
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

  • Be the first to comment on this lesson.