39 CSS Breadcrumbs03 / 39

Pure CSSMIT licensed

Adaptive Light & Dark Mode Breadcrumb Bar with CSS Custom Properties

A breadcrumb whose entire skin flips between light and dark from one set of custom properties — respecting the OS via prefers-color-scheme AND allowing a manual [data-theme] override. Frosted-glass bar background for modern SaaS dashboards, and every colour is a token, so re-theming is a five-line edit.

Published

Live Demo
Try it

The code

<div class="bc-03">
  <input type="checkbox" id="bc-03-theme" class="bc-03__check" aria-label="Toggle dark mode">
  <div class="bc-03__row">
    <nav class="bc-03__nav" aria-label="Breadcrumb">
      <ol>
        <li><a href="#">Dashboard</a></li>
        <li><a href="#">Projects</a></li>
        <li><a href="#">Aurora</a></li>
        <li><a href="#" aria-current="page">Settings</a></li>
      </ol>
    </nav>
    <label for="bc-03-theme" class="bc-03__toggle">
      <svg class="bc-03__sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
      <svg class="bc-03__moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
      <span class="bc-03__toggle-label">Theme</span>
    </label>
  </div>
</div>
.bc-03 {
  display: grid;
  width: 100%;
  min-height: 100vh;
  --bc-bg: oklch(1 0 0/.7);
  --bc-text: oklch(0.5 0.02 265);
  --bc-strong: oklch(0.25 0.02 265);
  --bc-accent: oklch(0.55 0.16 265);
  --bc-sep: oklch(0.75 0.02 265);
  --bc-hover: oklch(0.95 0.01 265);
  --bc-line: oklch(0.85 0.01 265/.7);
  color-scheme: light;
  font-family: system-ui,'Segoe UI',sans-serif;
  padding: 28px;
  background: oklch(0.97 0.005 265);
  place-items: center;
  transition: background-color .3s ease;
}

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

.bc-03:has(.bc-03__check:checked) {
  --bc-bg: oklch(0.2 0.02 265/.7);
  --bc-text: oklch(0.72 0.02 265);
  --bc-strong: oklch(0.96 0.01 265);
  --bc-accent: oklch(0.72 0.15 265);
  --bc-sep: oklch(0.5 0.02 265);
  --bc-hover: oklch(0.28 0.02 265);
  --bc-line: oklch(0.4 0.02 265/.5);
  color-scheme: dark;
  background: oklch(0.14 0.02 265);
}

.bc-03 ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 8px 14px;
  border-radius: 12px;
  background: var(--bc-bg);
  border: 1px solid var(--bc-line);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: background-color .3s ease,border-color .3s ease;
}

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

.bc-03 li + li::before {
  content: "›";
  margin: 0 8px;
  color: var(--bc-sep);
}

.bc-03 a {
  padding: 5px 9px;
  border-radius: 7px;
  font: 500 13.5px/1 system-ui,sans-serif;
  color: var(--bc-text);
  text-decoration: none;
  transition: background-color .18s ease,color .18s ease;
}

.bc-03 a:hover {
  color: var(--bc-strong);
  background: var(--bc-hover);
}

.bc-03 a:focus-visible {
  outline: 2px solid var(--bc-accent);
  outline-offset: 2px;
}

.bc-03 li[aria-current="page"] a {
  color: var(--bc-strong);
  font-weight: 600;
  background: color-mix(in oklab,var(--bc-accent) 18%,transparent);
}

@media (prefers-reduced-motion: reduce) {
  .bc-03 ol,
    .bc-03 a {
    transition: none;
  }
}

.bc-03 .bc-03__row {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
}

.bc-03 .bc-03__check {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}

.bc-03 .bc-03__toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 999px;
  background: #fff;
  border: 1px solid oklch(0.85 0.02 260);
  color: oklch(0.3 0.03 260);
  font: 600 12.5px/1 system-ui,sans-serif;
  cursor: pointer;
  transition: background .18s ease,border-color .18s ease,color .18s ease;
  user-select: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}

.bc-03 .bc-03__toggle:hover {
  background: color-mix(in oklch,currentColor 14%,transparent);
}

.bc-03 .bc-03__check:focus-visible + .bc-03__toggle {
  outline: 2px solid var(--accent,oklch(0.62 0.18 255));
  outline-offset: 2px;
}

.bc-03 .bc-03__toggle svg {
  width: 14px;
  height: 14px;
}

.bc-03 .bc-03__moon {
  display: none;
}

.bc-03:has(.bc-03__check:checked) .bc-03__toggle {
  background: oklch(0.22 0.02 260);
  border-color: oklch(0.35 0.02 260);
  color: oklch(0.95 0.005 260);
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}

.bc-03:has(.bc-03__check:checked) .bc-03__sun {
  display: none;
}

.bc-03:has(.bc-03__check:checked) .bc-03__moon {
  display: inline-block;
}
/* No JavaScript — five custom-property tokens skin the trail; prefers-color-scheme sets the OS default, [data-theme] overrides it manually. Add ~3 lines to persist the choice. */
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: Adaptive Light & Dark Mode Breadcrumb Bar with CSS Custom Properties
Source: https://codefronts.com/navigation/css-breadcrumbs/adaptive-light-dark-mode-breadcrumb-bar-with-css-custom-properties/

A breadcrumb whose entire skin flips between light and dark from one set of custom properties — respecting the OS via prefers-color-scheme AND allowing a manual [data-theme] override. Frosted-glass bar background for modern SaaS dashboards, and every colour is a token, so re-theming is a five-line edit.
## HTML
```html
<div class="bc-03">
  <input type="checkbox" id="bc-03-theme" class="bc-03__check" aria-label="Toggle dark mode">
  <div class="bc-03__row">
    <nav class="bc-03__nav" aria-label="Breadcrumb">
      <ol>
        <li><a href="#">Dashboard</a></li>
        <li><a href="#">Projects</a></li>
        <li><a href="#">Aurora</a></li>
        <li><a href="#" aria-current="page">Settings</a></li>
      </ol>
    </nav>
    <label for="bc-03-theme" class="bc-03__toggle">
      <svg class="bc-03__sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
      <svg class="bc-03__moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
      <span class="bc-03__toggle-label">Theme</span>
    </label>
  </div>
</div>
```
## CSS
```css
.bc-03 {
  display: grid;
  width: 100%;
  min-height: 100vh;
  --bc-bg: oklch(1 0 0/.7);
  --bc-text: oklch(0.5 0.02 265);
  --bc-strong: oklch(0.25 0.02 265);
  --bc-accent: oklch(0.55 0.16 265);
  --bc-sep: oklch(0.75 0.02 265);
  --bc-hover: oklch(0.95 0.01 265);
  --bc-line: oklch(0.85 0.01 265/.7);
  color-scheme: light;
  font-family: system-ui,'Segoe UI',sans-serif;
  padding: 28px;
  background: oklch(0.97 0.005 265);
  place-items: center;
  transition: background-color .3s ease;
}

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

.bc-03:has(.bc-03__check:checked) {
  --bc-bg: oklch(0.2 0.02 265/.7);
  --bc-text: oklch(0.72 0.02 265);
  --bc-strong: oklch(0.96 0.01 265);
  --bc-accent: oklch(0.72 0.15 265);
  --bc-sep: oklch(0.5 0.02 265);
  --bc-hover: oklch(0.28 0.02 265);
  --bc-line: oklch(0.4 0.02 265/.5);
  color-scheme: dark;
  background: oklch(0.14 0.02 265);
}

.bc-03 ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 8px 14px;
  border-radius: 12px;
  background: var(--bc-bg);
  border: 1px solid var(--bc-line);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: background-color .3s ease,border-color .3s ease;
}

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

.bc-03 li + li::before {
  content: "›";
  margin: 0 8px;
  color: var(--bc-sep);
}

.bc-03 a {
  padding: 5px 9px;
  border-radius: 7px;
  font: 500 13.5px/1 system-ui,sans-serif;
  color: var(--bc-text);
  text-decoration: none;
  transition: background-color .18s ease,color .18s ease;
}

.bc-03 a:hover {
  color: var(--bc-strong);
  background: var(--bc-hover);
}

.bc-03 a:focus-visible {
  outline: 2px solid var(--bc-accent);
  outline-offset: 2px;
}

.bc-03 li[aria-current="page"] a {
  color: var(--bc-strong);
  font-weight: 600;
  background: color-mix(in oklab,var(--bc-accent) 18%,transparent);
}

@media (prefers-reduced-motion: reduce) {
  .bc-03 ol,
    .bc-03 a {
    transition: none;
  }
}

.bc-03 .bc-03__row {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
}

.bc-03 .bc-03__check {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}

.bc-03 .bc-03__toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 999px;
  background: #fff;
  border: 1px solid oklch(0.85 0.02 260);
  color: oklch(0.3 0.03 260);
  font: 600 12.5px/1 system-ui,sans-serif;
  cursor: pointer;
  transition: background .18s ease,border-color .18s ease,color .18s ease;
  user-select: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}

.bc-03 .bc-03__toggle:hover {
  background: color-mix(in oklch,currentColor 14%,transparent);
}

.bc-03 .bc-03__check:focus-visible + .bc-03__toggle {
  outline: 2px solid var(--accent,oklch(0.62 0.18 255));
  outline-offset: 2px;
}

.bc-03 .bc-03__toggle svg {
  width: 14px;
  height: 14px;
}

.bc-03 .bc-03__moon {
  display: none;
}

.bc-03:has(.bc-03__check:checked) .bc-03__toggle {
  background: oklch(0.22 0.02 260);
  border-color: oklch(0.35 0.02 260);
  color: oklch(0.95 0.005 260);
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}

.bc-03:has(.bc-03__check:checked) .bc-03__sun {
  display: none;
}

.bc-03:has(.bc-03__check:checked) .bc-03__moon {
  display: inline-block;
}
```

## JavaScript
```js
/* No JavaScript — five custom-property tokens skin the trail; prefers-color-scheme sets the OS default, [data-theme] overrides it manually. Add ~3 lines to persist the choice. */
```

How this works

All colours live as custom properties on the component root: --bc-bg, --bc-text, --bc-accent, --bc-sep, --bc-hover. Dark is the default; a single @media (prefers-color-scheme: light) block reassigns the same tokens, so the trail follows the OS with no script. A manual override rides on top: [data-theme="light"] and [data-theme="dark"] attribute selectors set the tokens explicitly, letting a site's own theme switch win over the OS default. Because every rule reads var(--bc-*), the flip cascades through bar, links, separators and accent in one transition.

The bar itself is a modern SaaS treatment: a translucent fill with backdrop-filter:blur(8px) so it reads as frosted glass over whatever sits behind it, a hairline border from a token, and an accent-tinted current crumb. color-scheme is set on the root so native form controls and scrollbars flip with the theme too. This is exactly how a production design system themes a component — the demo just proves you don't need a JS ThemeProvider for the CSS half.

Make it yours

  • Add brand themes as extra attributes: [data-theme="ocean"] reassigning the same five tokens — the architecture scales to any palette.
  • Persist the manual choice with ~3 lines of JS writing data-theme to the root and localStorage; the CSS never changes.
  • --bc-accent alone recolours the current crumb, hover, and focus ring — one edit rebrands the trail.
  • Drop backdrop-filter for a solid bar if the trail never overlaps content; keep it for sticky dashboard headers.

Gotchas — read before shipping

  • Transition background-color and color specifically — never transition:all, which animates layout during theme flips and looks broken.
  • Set color-scheme on the root so native UI (scrollbars, date pickers) matches; without it they stay stark white in dark mode.
  • backdrop-filter needs something behind it to blur — over a flat page it just looks like a slightly transparent fill; that's expected.
  • Manual [data-theme] must beat the media query — keep its selector specificity ≥ the media-query rule (attribute on the same root element does this naturally).

Browser support

ChromeSafariFirefoxEdge
111+16.2+121+111+

Floor set by :has(), oklch(), color-mix(), backdrop-filter as this demo is written. The core technique itself goes back further — Chrome 76+.

prefers-color-scheme + custom properties are universal; backdrop-filter needs Safari's -webkit- prefix (included). oklch() styles the palette only.

Techniques used in this demo

Search CodeFronts

Loading…