Get eBook BundleVolume I index
JSX Is Real React Code

Fragments and Nested Trees

Ishtmeet Singh @ishtms/July 20, 2026/9 min read
#react#jsx#fragments#html#components

A React component has to return one JavaScript value. Fine. But what if you want to return two elements next to each other, and you don't actually want another HTML element wrapping them?

That's exactly what fragments are for.

jsx
function ChapterHeading() {
  return (
    <>
      <p>Chapter 3</p>
      <h1>JSX Is Real React Code</h1>
    </>
  );
}

That <> and </> pair is the short fragment syntax. React can treat those two children as one grouped result, while React DOM still creates only the p and h1 in the actual DOM.

So no extra div, no fake wrapper added just because JSX wanted one return value.

And honestly, that's most of what you need to know about fragments. React needs some way to group sibling nodes, but sometimes the HTML document has absolutely no reason to contain another parent element. In those cases, use a fragment.

Check What Actually Reaches the DOM

Let's put that ChapterHeading component inside a real header.

jsx
function App() {
  return (
    <header>
      <ChapterHeading />
    </header>
  );
}

Now if you inspect the page in browser DevTools, you'll get a DOM structure equivalent to this:

html
<header>
  <p>Chapter 3</p>
  <h1>JSX Is Real React Code</h1>
</header>

There isn't some secret <Fragment> element sitting between header and those children. React knows about the fragment while working with the React tree, but React DOM doesn't create a DOM node for it.

And yes, this also affects CSS selectors exactly how you'd expect.

css
header > h1 {
  color: #61dafb;
}

That selector still matches the h1, because the heading really is a direct child of header in the DOM. The fragment never became an HTML element in between.

Sometimes You Actually Need a Real Parent

Now don't start replacing every div, section, header, and article with fragments just because you discovered <>...</>.

Take this component:

jsx
function LessonOverview() {
  return (
    <section aria-labelledby="lesson-overview-title">
      <h2 id="lesson-overview-title">Lesson overview</h2>
      <p>Fragments and nested trees</p>
    </section>
  );
}

That section is doing a real job. It says these elements belong together as one section of the document, and the aria-labelledby connects that section with its heading.

If you replace the section with a fragment, you've removed that parent from the HTML completely.

And sometimes you need a parent because you want to put a class on it, attach an event handler, give it accessibility information, grab it with a ref, use something such as hidden, or make it the layout container for its children.

A fragment can't do those DOM jobs because there is no DOM element there.

This, for example, doesn't give you a styled wrapper:

jsx
<Fragment className="panel">...</Fragment>

There is no browser element for that class to end up on.

Don't Add HTML Just to Make JSX Happy

Tables make this really obvious.

Suppose you want a component that returns two cells:

jsx
function LessonCells() {
  return (
    <>
      <td>Fragments</td>
      <td>12 min</td>
    </>
  );
}

Then use it inside a row:

jsx
<tr>
  <LessonCells />
</tr>

The final DOM can have the two td elements directly inside the tr, which is what you wanted.

If you wrapped those cells in a div just so the component could return one parent, you'd now have a div sitting inside a table row where it shouldn't be.

Fragments let you satisfy React's grouping requirement without adding that extra HTML node.

But don't take this to mean fragments can somehow bypass normal HTML nesting rules.

A list still needs list items:

jsx
<ul>
  <li>Expressions Inside JSX</li>
  <li>Attributes and Styles</li>
</ul>

Putting a fragment inside ul and then returning random headings and text doesn't suddenly make that valid list markup. The fragment disappears from the DOM, so the browser still sees whatever children you actually returned.

HTML rules are still HTML rules.

The Short Fragment Syntax Can't Take Props

Most of the time you'll write fragments using the short syntax:

jsx
<>
  <dt>Fragment</dt>
  <dd>A React grouping value</dd>
</>

But there's no actual tag name there, so you can't attach props to it.

You can't do this:

jsx
< key={term.id}>
  <dt>{term.name}</dt>
  <dd>{term.definition}</dd>
</>

That's just invalid JSX.

If you need a key, use the named Fragment form.

jsx
import { Fragment } from "react";

Then:

jsx
<Fragment key={term.id}>
  <dt>{term.name}</dt>
  <dd>{term.definition}</dd>
</Fragment>

This comes up quite a lot when each item in a rendered list needs to return multiple sibling elements. The key belongs to the whole group, so the named fragment gives you somewhere to put it.

If you don't need a key, <>...</> is usually nicer to read.

A Component Can Return a Fragment at the Top

A fragment can also be the top-level result of a component.

jsx
function LessonMetadata() {
  return (
    <>
      <span>Chapter 3</span>
      <span>Lesson 3</span>
      <span>12 min</span>
    </>
  );
}

LessonMetadata() still returns one React value as far as React is concerned, but once React DOM produces the browser DOM, you end up with three sibling span elements.

Then the parent component can decide whether those spans need a real wrapper.

jsx
<div className="lesson-metadata">
  <LessonMetadata />
</div>

In this version, the div owns the layout job and LessonMetadata only provides the three pieces of metadata.

That's perfectly fine if this component is always being used somewhere that already provides the container.

But maybe later LessonMetadata itself needs an accessible label, its own layout styling, a ref, or some other DOM-level property. At that point, giving the component a real parent element probably makes more sense.

Don't choose fragments because fewer DOM nodes somehow sounds automatically better. Choose them because you genuinely don't need a parent node there.

Fragments Don't Create Their Own CSS Container

Since a fragment doesn't become a DOM element, CSS has nothing special to attach to.

jsx
function LessonMetadata() {
  return (
    <>
      <span className="label">Chapter 3</span>
      <span className="label">Lesson 3</span>
    </>
  );
}

Those spans are styled exactly according to their real location in the DOM. They inherit from their actual parent, and normal selectors from the surrounding page can match them.

The fragment doesn't create some separate CSS scope around them.

If you need an element specifically for layout or styling, then use one.

jsx
function LessonMetadata() {
  return (
    <div className="lesson-metadata">
      <span>Chapter 3</span>
      <span>Lesson 3</span>
    </div>
  );
}

Now that div has a real purpose. You're using it because the browser needs a container you can style, not because JSX forced you to invent one.

Fragments Don't Catch Events Either

Same story with browser events.

jsx
<section onClick={handleSectionClick}>
  <>
    <button type="button">Open</button>
    <button type="button">Save</button>
  </>
</section>

Click either button and the event can propagate up to the section.

There's no fragment DOM node in between where you could attach some browser event listener or stop propagation. The browser only knows about the section and the two buttons.

Again, React has a fragment in its own tree. The browser doesn't.

Components and DOM Elements Are Two Different Trees to Read

You can also have nested components returning completely different HTML elements.

jsx
function LessonArea() {
  return (
    <section>
      <LessonNotice />
      <LessonList />
    </section>
  );
}

Maybe LessonNotice returns a paragraph and LessonList returns a list.

You could describe the React side roughly like this:

text
LessonArea component
  section host element
    LessonNotice component
      p host element
    LessonList component
      ul host element

React sees LessonArea, LessonNotice, and LessonList as components while it's rendering the component tree.

The browser doesn't get elements with those names. Once everything has been resolved, the browser gets the real host elements those components returned, such as section, p, and ul.

This is also why React DevTools and the browser Elements panel can show you very different-looking trees. React DevTools cares about your components. The Elements panel shows the actual DOM.

Both views are useful, they're just showing different things.

Please Don't Wrap One Child in a Fragment

You will sometimes see this:

jsx
function LessonTitle() {
  return (
    <>
      <h2>Fragments</h2>
    </>
  );
}

There's nothing for the fragment to group.

Just return the heading:

jsx
function LessonTitle() {
  return <h2>Fragments</h2>;
}

Will the extra fragment destroy performance and crash your app? No.

But it adds syntax for no reason and makes somebody reading the component wonder whether multiple siblings are supposed to appear there later.

If there's only one child, just return the child.

Decide by Looking at the HTML You Want

When you're unsure whether to use a fragment or an element, forget JSX for a second and ask what DOM you actually want the browser to have.

Maybe you want this:

text
article
  header
    p
    h2
  p

Well, header is meaningful HTML there. Keep it.

Now maybe you're returning cells and want this:

text
tr
  td
  td

Then a fragment-returning component can give you those two cells without inserting another element between tr and td.

That's usually enough to make the decision.

If the browser needs a real parent because that parent has semantic meaning, styling, accessibility information, events, a ref, or some DOM property, use an HTML element.

If React only needs to bundle a few siblings into one return value and the browser doesn't need another parent, use a fragment.

And if you're still not sure, render it and open the Elements panel. The DOM there is the thing your browser is actually working with.