CSS Display Contents

The display property decides how an element participates in layout: does it make a box, does it flow inline, does it become a flex/grid container, etc.

And then there’s display: contents, which is basically: “pretend this element’s box does not exist… but keep its children.”

That sounds magical (and sometimes it is), but it also comes with important tradeoffs—especially for background/border/padding and accessibility.

CSS display contents property

When you set display: contents on an element:

  • The element does not generate its own box in the layout.
  • Its children behave as if they were direct children of the element’s parent.
  • The element’s own background, border, and padding won’t render, because there is no box to paint them on.

Think of it as “remove the wrapper box” without removing the wrapper node from the DOM.

First intuition: a wrapper that doesn’t wrap

.wrapper {
  display: block;
}
.wrapper {
  display: contents;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.outer {
  border: 3px solid #111;
  padding: 12px;
  display: flex;
  gap: 12px;
  align-items: center;
}

.wrapper {
  border: 3px dashed #111;
  padding: 12px;
}

.badge {
  display: inline-block;
  border: 2px solid #111;
  padding: 6px 10px;
  font-weight: 700;
}

.note {
  opacity: 0.85;
}
Child A Child B Child C

Toggle the snippet. With contents, the dashed wrapper box disappears.

In the first snippet, the wrapper has a visible dashed border and padding, because it creates a box. In the second snippet, the wrapper creates no box, so the dashed border and padding vanish—yet the children still render and sit inside the flex layout.

CSS display contents example

The most common “wow” use-case is when a wrapper element breaks a layout, especially in CSS Grid.

A classic example: you want a two-column form grid where label is column 1 and input is column 2. But your HTML groups each field inside a wrapper like <div class="field">. That wrapper becomes the grid item, so the label/input can’t align across rows.

Example: grid form alignment with display: contents

.field {
  display: block;
}
.field {
  display: contents;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.form {
  border: 3px solid #111;
  padding: 16px;
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 10px 12px;
  max-width: 560px;
}

label {
  font-weight: 700;
  align-self: center;
}

input,
select {
  border: 2px solid #111;
  padding: 10px 12px;
  font: inherit;
  width: 100%;
}

.help {
  grid-column: 1 / -1;
  opacity: 0.85;
  margin-top: 6px;
}

Toggle the snippet. With contents, labels line up in column 1 and inputs in column 2.

What’s happening:

  • With .field { display: block; }, each .field is a single grid item, so the grid sees three items (one per row), and the label/input can’t become separate columns.
  • With .field { display: contents; }, .field stops generating a box, so its children (label and input/select) become the grid items. Now the grid can place each label in column 1 and each control in column 2 automatically.
  • It's a bit like CSS Subgrid, but without needing to make the inner element a grid container. The children just step up and become part of the parent grid. Also this display: contents method has ~96% browser support, while subgrid is at ~88%.

CSS display contents background color / CSS display contents border

This is the part that surprises people (usually the hard way):

  • If the element doesn’t generate a box, it has nowhere to paint a background.
  • No box also means no border and no padding.

In other words: you can’t “style the wrapper” visually if you remove the wrapper’s box.

Background, border, padding demo

.panel {
  display: block;
}
.panel {
  display: contents;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.shell {
  border: 3px solid #111;
  padding: 14px;
  max-width: 620px;
}

.panel {
  background: #eee;
  border: 3px dashed #111;
  padding: 14px;
}

.panel h4 {
  margin: 0 0 8px 0;
}

.pill {
  display: inline-block;
  border: 2px solid #111;
  padding: 6px 10px;
  font-weight: 700;
  margin-right: 6px;
}

Wrapper styles live here

One Two Three

Notice how the dashed border and gray background vanish when display: contents is active. The children remain, but the wrapper styling is gone.

So how do I get a border then?

You have a few options:

  • Move the border/background/padding to a real box (often a child).
  • Add a “visual wrapper” element inside, and put display: contents on the outer wrapper only.
  • If you only needed spacing, prefer gap on the parent grid/flex instead of padding on the removed wrapper.

CSS display contents alternatives

display: contents is best when:

  • You need the children to participate directly in a parent layout (grid/flex).
  • The wrapper is present for structure, grouping, hooks, or scripting—but not for visuals.

If that’s not your situation, these alternatives are often safer (and sometimes simpler):

Alternative 1: change the HTML structure

If you control the markup, the cleanest fix is often: don’t add the wrapper in the first place.

For example, in a form grid you can place label and input as direct children of the grid container (no .field wrapper). It’s boring, but boring is stable.

Alternative 2: use grid-template-areas

When wrappers exist for a reason, you can still get aligned layouts using named areas, or by explicitly placing items. This is more CSS, but it avoids removing boxes and avoids accessibility surprises.

Alternative 3: subgrid (when the problem is nested grids)

Sometimes you’re reaching for display: contents because you want a nested grid to align with the parent grid. That’s exactly the job of subgrid (where supported).

Rule of thumb: if your wrapper exists because it’s a component boundary (card, row, field), subgrid can be a better semantic fit than “erase the wrapper box.”

Learn more about subgrid in the CSS Subgrid Interactive Tutorial.

Alternative 4: flexbox or flow layout

If the only reason you’re using grid is “two columns,” flexbox or regular flow might be enough—especially for simple layouts.

Learn more about flexbox in the CSS Flexbox Interactive Tutorial.

CSS display contents support (browser support)

Browser support for display: contents is broadly good. On Can I use, global support is around 95.83% (so roughly ~96% at the time of writing).

Here’s the Can I use page: https://caniuse.com/css-display-contents

Support is not the whole story, though. The bigger practical concern today is usually accessibility behavior, not “does it render.”

CSS display contents accessibility

Some browser implementations have had (and some still have) cases where an element with display: contents is treated incorrectly in the accessibility tree. MDN notes that some browsers remove the element itself from the accessibility tree (while leaving descendants), which can cause the element to no longer be announced by screen readers—even though that’s not what the spec intends.

What can go wrong in real life

  • A wrapper with semantic meaning (or ARIA) becomes “invisible” to assistive tech.
  • If you put aria-label, role, or relationships on the wrapper, those may not behave how you expect when the wrapper has no box and is mishandled in the accessibility tree.
  • Certain patterns (notably complex table navigation in some screen reader + browser combos) have historically been buggy when display properties like contents are involved.

Safe patterns

If you want a simple safety rule that will keep you out of trouble most of the time:

  • Use display: contents on neutral wrappers like div elements that exist only for layout grouping.
  • Avoid using it on semantic elements that carry meaning by themselves (for example: button, fieldset, label, nav, main, section, lists, table structures, etc.).
  • Avoid using it on elements that carry important ARIA roles/labels/relationships.

Accessibility-friendly form grid pattern

In the form grid demo above, we used display: contents on .field, which is a neutral wrapper. That’s typically the “sweet spot” use-case.

The labels and inputs remain native, focus works normally, and you didn’t erase the semantics of the important elements themselves.

A practical testing checklist

  • Navigate the UI with only a keyboard (Tab, Shift+Tab, Enter, Space).
  • If you can, test with at least one screen reader + browser combo (for example: VoiceOver + Safari on macOS, or NVDA + Firefox on Windows).
  • If the wrapper had ARIA (labels/roles), verify those announcements still happen.
  • If anything “goes silent” or navigation becomes weird, remove display: contents and use one of the alternatives instead.

Quick recap: when to use display: contents

  • Use it when a wrapper breaks grid/flex layout and you need the children to become layout items.
  • Don’t expect borders/backgrounds/padding to work on that element (no box, no paint).
  • Be cautious with accessibility: keep it on neutral wrappers, and test your UI with keyboard and (ideally) a screen reader.

One last pro tip

If you find yourself adding display: contents everywhere, it might be a sign your HTML structure (or component boundaries) and your layout strategy are fighting each other. Sometimes the best CSS trick is a markup refactor.