39 CSS Breadcrumbs02 / 39

Pure CSSMIT licensed

Responsive Collapsing CSS Breadcrumb Bar using :has() Selector

A breadcrumb that truncates itself on narrow screens with ZERO JavaScript: intermediate crumbs collapse behind an ellipsis, and hovering or focusing the ellipsis expands them back with a smooth max-width tween. Container queries make it respond to its own column width, so it collapses in a narrow sidebar even on a wide screen.

Published

Live Demo
Try it

The code

<div class="bc-02">
  <nav aria-label="Breadcrumb">
    <ol>
      <li><a href="#">Home</a></li>
      <li class="bc-02__ellipsis"><button type="button" aria-label="Show collapsed breadcrumbs">&hellip;</button></li>
      <li><a href="#">Store</a></li>
      <li><a href="#">Women</a></li>
      <li><a href="#">Outerwear</a></li>
      <li><a href="#" aria-current="page">Wool Coat</a></li>
    </ol>
  </nav>
</div>
.bc-02 {
  display: grid;
  width: 100%;
  min-height: 100vh;
  container-type: inline-size;
  font-family: system-ui,'Segoe UI',sans-serif;
  padding: 28px;
  background: oklch(0.14 0.02 265);
  color: oklch(0.9 0.01 265);
  place-items: center;
}

.bc-02 * {
  box-sizing: border-box;
}

.bc-02 ol {
  display: flex;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}

.bc-02 li {
  display: flex;
  align-items: center;
}

.bc-02 li + li::before {
  content: "›";
  margin: 0 8px;
  color: oklch(0.55 0.02 265);
}

.bc-02 a {
  white-space: nowrap;
  overflow: hidden;
  max-width: 14ch;
  text-overflow: ellipsis;
  padding: 5px 4px;
  border-radius: 6px;
  color: oklch(0.78 0.03 265);
  text-decoration: none;
  transition: max-width .3s ease,opacity .25s ease,color .15s ease;
}

.bc-02 a:hover {
  color: #fff;
}

.bc-02 a:focus-visible {
  outline: 2px solid oklch(0.72 0.15 265);
  outline-offset: 2px;
}

.bc-02 li[aria-current="page"] a {
  color: #fff;
  font-weight: 600;
}

.bc-02__ellipsis {
  display: none;
}

.bc-02__ellipsis button {
  display: grid;
  place-items: center;
  min-width: 34px;
  height: 30px;
  padding: 0 8px;
  border: none;
  border-radius: 8px;
  background: oklch(0.24 0.02 265);
  color: oklch(0.8 0.02 265);
  font: 700 15px/1 system-ui,sans-serif;
  cursor: pointer;
}

.bc-02__ellipsis button:focus-visible {
  outline: 2px solid oklch(0.72 0.15 265);
  outline-offset: 2px;
}

@container (max-width: 480px) {
  .bc-02 ol:has(.bc-02__ellipsis) li:not(:first-child):not(:last-child):not(.bc-02__ellipsis) a {
    max-width: 0;
    opacity: 0;
    padding-inline: 0;
  }

  .bc-02 ol:has(.bc-02__ellipsis) li:not(:first-child):not(:last-child):not(.bc-02__ellipsis)::before {
    display: none;
  }

  .bc-02__ellipsis {
    display: flex;
  }

  .bc-02 ol:hover li a,
    .bc-02 ol:focus-within li a {
    max-width: 14ch;
    opacity: 1;
    padding-inline: 4px;
  }

  .bc-02 ol:hover li::before,
    .bc-02 ol:focus-within li::before {
    display: flex;
  }

  .bc-02 ol:hover .bc-02__ellipsis,
    .bc-02 ol:focus-within .bc-02__ellipsis {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bc-02 a {
    transition: none;
  }
}
/* No JavaScript — container queries collapse intermediate crumbs to max-width:0 behind an ellipsis; ol:hover/:focus-within tweens them back. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Breadcrumb from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Responsive Collapsing CSS Breadcrumb Bar using :has() Selector
Source: https://codefronts.com/navigation/css-breadcrumbs/responsive-collapsing-css-breadcrumb-bar-using-has-selector/

A breadcrumb that truncates itself on narrow screens with ZERO JavaScript: intermediate crumbs collapse behind an ellipsis, and hovering or focusing the ellipsis expands them back with a smooth max-width tween. Container queries make it respond to its own column width, so it collapses in a narrow sidebar even on a wide screen.
## HTML
```html
<div class="bc-02">
  <nav aria-label="Breadcrumb">
    <ol>
      <li><a href="#">Home</a></li>
      <li class="bc-02__ellipsis"><button type="button" aria-label="Show collapsed breadcrumbs">&hellip;</button></li>
      <li><a href="#">Store</a></li>
      <li><a href="#">Women</a></li>
      <li><a href="#">Outerwear</a></li>
      <li><a href="#" aria-current="page">Wool Coat</a></li>
    </ol>
  </nav>
</div>
```
## CSS
```css
.bc-02 {
  display: grid;
  width: 100%;
  min-height: 100vh;
  container-type: inline-size;
  font-family: system-ui,'Segoe UI',sans-serif;
  padding: 28px;
  background: oklch(0.14 0.02 265);
  color: oklch(0.9 0.01 265);
  place-items: center;
}

.bc-02 * {
  box-sizing: border-box;
}

.bc-02 ol {
  display: flex;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}

.bc-02 li {
  display: flex;
  align-items: center;
}

.bc-02 li + li::before {
  content: "›";
  margin: 0 8px;
  color: oklch(0.55 0.02 265);
}

.bc-02 a {
  white-space: nowrap;
  overflow: hidden;
  max-width: 14ch;
  text-overflow: ellipsis;
  padding: 5px 4px;
  border-radius: 6px;
  color: oklch(0.78 0.03 265);
  text-decoration: none;
  transition: max-width .3s ease,opacity .25s ease,color .15s ease;
}

.bc-02 a:hover {
  color: #fff;
}

.bc-02 a:focus-visible {
  outline: 2px solid oklch(0.72 0.15 265);
  outline-offset: 2px;
}

.bc-02 li[aria-current="page"] a {
  color: #fff;
  font-weight: 600;
}

.bc-02__ellipsis {
  display: none;
}

.bc-02__ellipsis button {
  display: grid;
  place-items: center;
  min-width: 34px;
  height: 30px;
  padding: 0 8px;
  border: none;
  border-radius: 8px;
  background: oklch(0.24 0.02 265);
  color: oklch(0.8 0.02 265);
  font: 700 15px/1 system-ui,sans-serif;
  cursor: pointer;
}

.bc-02__ellipsis button:focus-visible {
  outline: 2px solid oklch(0.72 0.15 265);
  outline-offset: 2px;
}

@container (max-width: 480px) {
  .bc-02 ol:has(.bc-02__ellipsis) li:not(:first-child):not(:last-child):not(.bc-02__ellipsis) a {
    max-width: 0;
    opacity: 0;
    padding-inline: 0;
  }

  .bc-02 ol:has(.bc-02__ellipsis) li:not(:first-child):not(:last-child):not(.bc-02__ellipsis)::before {
    display: none;
  }

  .bc-02__ellipsis {
    display: flex;
  }

  .bc-02 ol:hover li a,
    .bc-02 ol:focus-within li a {
    max-width: 14ch;
    opacity: 1;
    padding-inline: 4px;
  }

  .bc-02 ol:hover li::before,
    .bc-02 ol:focus-within li::before {
    display: flex;
  }

  .bc-02 ol:hover .bc-02__ellipsis,
    .bc-02 ol:focus-within .bc-02__ellipsis {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bc-02 a {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — container queries collapse intermediate crumbs to max-width:0 behind an ellipsis; ol:hover/:focus-within tweens them back. */
```

How this works

The trail wraps in a container-type:inline-size element, so the collapse is driven by the component's OWN width, not the viewport — drop it in a 300px column on a 4K monitor and it still collapses. Under @container (max-width:480px), every intermediate crumb (li:not(:first-child):not(:last-child)) animates its label to max-width:0;opacity:0 and an ellipsis <li> (hidden at full width via :has()) fades in. The user always sees Home … Current page — the two anchors that matter on mobile.

Expansion is pure CSS: .bc-02 ol:hover / ol:focus-within transitions the intermediate labels back to max-width:12ch, so hovering the bar or tabbing into it reveals the full path. Animating max-width (not display) is what makes the reveal smooth — display:none can't tween. The ellipsis is a real focusable control so keyboard users can trigger the same expand, and prefers-reduced-motion collapses the transition to an instant swap.

Make it yours

  • Collapse threshold is the @container value — 480px suits a main-content trail; tighten to 320px for a persistent sidebar.
  • Keep more context by exempting the second crumb too: li:not(:nth-child(-n+2)):not(:last-child).
  • Replace hover-expand with click-expand using a checkbox + :has(:checked) if your users are touch-first.
  • max-width:12ch is the reveal width — raise it for long category names, or add ellipsis inside for a hybrid.

Gotchas — read before shipping

  • max-width animation needs a concrete end value — you can't tween to max-width:none; pick a generous ch/px cap.
  • The ellipsis crumb must stay in the DOM and focusable — if you display:none it, keyboard users lose the expand affordance; use opacity/visibility.
  • Container queries need a sized container — the wrapper must have a resolvable inline-size (block or explicit width), or the query never fires.
  • :focus-within keeps the trail open while any inner link is focused; without it, tabbing past the first hidden link would re-collapse mid-navigation.

Browser support

ChromeSafariFirefoxEdge
111+16+121+111+

Floor set by :has(), oklch(), @container as this demo is written. The core technique itself goes back further — Chrome 105+.

:has() + size container queries are the gate — both baseline since 2023. Fallback: without :has() the ellipsis simply shows alongside (still usable).

Techniques used in this demo

Search CodeFronts

Loading…