A `<Fields>` React component wraps the inputs it returns in a `<div>` so that it can return several elements. Once it is placed inside a `display: grid` form container, everything it renders lands in a single grid cell instead of flowing across the grid's columns. Explain the mechanism and the fix.
answer
- only direct children are items
- one wrapper, one grid cell
- the styles are fine, the tree is not
- a grouping node with no box
- replace the div with a fragment
basics
~20 sGrid and flex containers lay out only their direct children, and the wrapper div is now the sole direct child, so it becomes one grid item holding everything. Replace the wrapper with a fragment so the inputs become direct children of the grid again.
solid answer
~50 sThe wrapper is a real element, and CSS grid only distributes its *direct children* into tracks. From the grid's point of view the form has one child — the div — so it creates one item and everything the component renders is laid out inside it by normal block flow. Nothing about React is broken; the component silently imposed a DOM level on its parent's layout. The fix is to return the inputs inside a fragment, `<>...</>`, which gives React its single root while contributing no node, so the inputs are direct children of the `<form>` and become grid items themselves. The general lesson is that a component's returned DOM structure is part of its contract: if a wrapper exists only because "JSX needs one root", it should be a fragment. Keep a real element only when the group deserves a box of its own — a background, a ref, or an ARIA role.
code
jsx · 16 linesfunction Fields({ user }) {
return (
<>
<label htmlFor="name">Name</label>
<input id="name" defaultValue={user.name} />
</>
);
}
function ProfileForm({ user }) {
return (
<form style={{ display: 'grid', gridTemplateColumns: '8rem 1fr', gap: '0.5rem' }}>
<Fields user={user} />
</form>
);
}go deeper
Remember the rule that flex and grid arrange only the container's direct children, so an extra element between the container and your content becomes the single item. Reach for <>...</> when a component wraps things purely to return them together.
Explain the mechanism precisely: the wrapper establishes a new level, so the tracks, gap, and direct-child selectors apply to it rather than to the content inside. Be able to say the CSS is correct and the tree is wrong.
Diagnose from the symptom — layout that works inline but breaks once extracted into a component — and argue why deleting the node beats neutralising it with display: contents. Know the second-order damage too: invalid nesting in tables and lists, and severed list semantics.
Own the contract: a shared component's rendered DOM shape is part of its public surface, and gratuitous wrappers make it uncomposable in every consumer's layout. Set the review expectation early, because restructuring markup after dozens of screens depend on it is far more expensive than catching it once.
## What the grid actually sees CSS grid and flexbox establish a formatting context on the container, and the items that context lays out are the container's *direct children*. Not descendants — children. This is the entire mechanism behind the bug. Write the form as a two-column grid and the intent is that each label goes in column one and each input in column two. Compose it out of a `<Fields>` component that returns: ```jsx function Fields({ user }) { return ( <div> <label htmlFor="name">Name</label> <input id="name" defaultValue={user.name} /> </div> ); } ``` and the `<form>` now has exactly one child. Grid creates one item for it. The label and input are laid out *inside* that item by ordinary block flow — stacked, full width, ignoring the track definitions, the `gap`, and any `grid-column` rules you wrote. The styles are all still correct; they are simply being applied to a level of the tree your content no longer occupies. The same failure appears with `gap`, with `flex: 1`, with `justify-content`, and with any `:nth-child()` or `>` selector aimed at the fields. ## Diagnosing it The tell in the browser's element inspector is an unexpected node between the container and the content, and a grid overlay showing one item where you expected several. The tell in review is a wrapper element with no class, no role, and no styles — an element that exists only to satisfy the single-root rule. Whenever layout "works when I inline the markup but breaks when I extract a component", suspect exactly this. ## The fix Return a fragment: ```jsx function Fields({ user }) { return ( <> <label htmlFor="name">Name</label> <input id="name" defaultValue={user.name} /> </> ); } ``` A fragment gives React the single root the JSX expression needs and renders nothing itself, so the label and the input become direct children of the `<form>`. They are now grid items, the tracks apply, and the component composes into any parent layout without dictating structure. ## Where else the stray wrapper bites Layout is only the most visible symptom. **Invalid HTML nesting.** Certain parents accept only specific children. A `<div>` between `<tr>` and `<td>`, or between `<ul>` and `<li>`, or between `<dl>` and `<dt>`, is not permitted. When markup like that is parsed from HTML text the browser repairs it, typically by moving the offending content out of the table entirely, so the rendered result differs from the tree you authored. **Semantics.** A list whose `<li>` elements sit under an intermediate `<div>` is no longer reliably a list to assistive technology; the implicit relationship between the `<ul>` and its items has been severed. The wrapper adds a node with no meaning while destroying meaning that was there. **Selectors.** `.form > label`, `input + button`, and `:nth-child()` count the real tree, so an inserted level silently unmatches rules written against the content. ## Why not `display: contents`? Setting `display: contents` on the wrapper removes its own box so the children participate in the parent's layout, and it does resolve the grid symptom. It is a legitimate escape hatch when you do not control the markup — a third-party component, or an element you need for another reason. It is the wrong first answer here, because the node still exists in the document and the tree, the fix has to be remembered by every future reader of the CSS, and browsers have had accessibility bugs where removing an element's box also removed the semantics of elements like tables and lists. Deleting the element is strictly simpler than neutralising it. ## When the wrapper is right This is not a rule against `<div>`. Keep a real element when the group is genuinely a thing: it needs a background, a border, padding, a ref for measurement, a scroll container, a click target, or a landmark or ARIA role. The test is whether the box means something. "JSX made me" is not a meaning. ## The design point The deeper lesson is about contracts. A component's rendered DOM shape is part of what it promises its callers, and a component that emits an unnecessary container is a component that cannot be dropped into an arbitrary layout. In a shared library that cost compounds: every consumer either fights the wrapper with `display: contents`, or duplicates the component, or moves the layout up a level. Reviewing extracted components for gratuitous wrappers is cheap; unpicking them once dozens of screens depend on the structure is not.
- When is keeping the wrapper `<div>` the right call rather than a fragment?When the box means something: the group needs a background, border, padding, a scroll container, a ref for measurement, an event target, or a landmark or ARIA role. Those are all reasons the element earns its place. The wrapper to delete is the one whose only justification is that JSX requires a single root.
- Besides grid and flex layout, where else does an unnecessary wrapper element cause real breakage?Inside constrained parents. A `<div>` is invalid between `<tr>` and `<td>`, or between `<ul>` and `<li>`, so parsed markup gets repaired and relocated by the browser. It also severs implicit list and table semantics for assistive technology, and unmatches every direct-child and adjacent-sibling CSS selector aimed at the content.
- Someone proposes fixing this with `display: contents` on the wrapper instead. What do you say?It works and is a fair escape hatch when you do not control the markup, but it is the weaker fix here. The node still exists in the document and tree, the workaround must be remembered by every later reader, and browsers have had bugs where removing the box also removed list and table semantics. Deleting the element beats neutralising it.
saying these in an interview costs you the question
- Blames the CSS and starts adding grid-column rules to the wrapper
- Thinks grid lays out all descendants, not just direct children
- Reaches for display: contents before questioning the wrapper
- Says wrapper divs only cost a few bytes of DOM
- Concludes every div is bad and strips meaningful containers too