39 CSS Breadcrumbs39 / 39
Modern Micro-Interaction Animated Hover Breadcrumbs
A breadcrumb built around a delightful hover micro-interaction: each crumb lifts with a subtle translateY, gains a soft shadow, and its separator chevron nudges forward — small, spring-eased motions that make the trail feel responsive and alive without being distracting. Compositor-only and reduced-motion-safe.
Published Updated
The code
<nav class="bc-39" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Hardware</a></li>
<li><a href="#" aria-current="page">Keyboards</a></li>
</ol>
</nav><nav class="bc-39" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Hardware</a></li>
<li><a href="#" aria-current="page">Keyboards</a></li>
</ol>
</nav>.bc-39 {
display: grid;
width: 100%;
min-height: 100vh;
--accent: oklch(0.58 0.15 285);
font-family: system-ui,'Segoe UI',sans-serif;
padding: 34px 30px;
background: oklch(0.98 0.004 285);
place-items: center;
}
.bc-39 * {
box-sizing: border-box;
}
.bc-39 ol {
display: flex;
flex-wrap: wrap;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
}
.bc-39 li {
display: flex;
align-items: center;
}
.bc-39 li:not(:last-child)::after {
content: "›";
margin: 0 8px;
color: oklch(0.7 0.02 285);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),color .18s ease;
}
.bc-39 li:has(a:hover):not(:last-child)::after {
transform: translateX(3px);
color: var(--accent);
}
.bc-39 a {
display: inline-block;
padding: 7px 12px;
border-radius: 9px;
font: 600 13.5px/1 system-ui,sans-serif;
color: oklch(0.45 0.04 285);
text-decoration: none;
background: transparent;
box-shadow: 0 0 0 rgba(40,20,80,0);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,color .18s ease,background-color .18s ease;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: translateY(-2px);
color: var(--accent);
background: #fff;
box-shadow: 0 6px 16px rgba(40,20,80,.14);
outline: none;
}
.bc-39 a:focus-visible {
box-shadow: 0 6px 16px rgba(40,20,80,.14),0 0 0 2px var(--accent);
}
.bc-39 li[aria-current="page"] a {
color: oklch(0.3 0.02 285);
font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
.bc-39 a,
.bc-39 li::after {
transition: none;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: none;
}
}.bc-39 {
display: grid;
width: 100%;
min-height: 100vh;
--accent: oklch(0.58 0.15 285);
font-family: system-ui,'Segoe UI',sans-serif;
padding: 34px 30px;
background: oklch(0.98 0.004 285);
place-items: center;
}
.bc-39 * {
box-sizing: border-box;
}
.bc-39 ol {
display: flex;
flex-wrap: wrap;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
}
.bc-39 li {
display: flex;
align-items: center;
}
.bc-39 li:not(:last-child)::after {
content: "›";
margin: 0 8px;
color: oklch(0.7 0.02 285);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),color .18s ease;
}
.bc-39 li:has(a:hover):not(:last-child)::after {
transform: translateX(3px);
color: var(--accent);
}
.bc-39 a {
display: inline-block;
padding: 7px 12px;
border-radius: 9px;
font: 600 13.5px/1 system-ui,sans-serif;
color: oklch(0.45 0.04 285);
text-decoration: none;
background: transparent;
box-shadow: 0 0 0 rgba(40,20,80,0);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,color .18s ease,background-color .18s ease;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: translateY(-2px);
color: var(--accent);
background: #fff;
box-shadow: 0 6px 16px rgba(40,20,80,.14);
outline: none;
}
.bc-39 a:focus-visible {
box-shadow: 0 6px 16px rgba(40,20,80,.14),0 0 0 2px var(--accent);
}
.bc-39 li[aria-current="page"] a {
color: oklch(0.3 0.02 285);
font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
.bc-39 a,
.bc-39 li::after {
transition: none;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: none;
}
}/* No JavaScript — a 2px translateY lift + soft shadow on hover, and a :has()-driven chevron nudge toward the next crumb, all spring-eased and compositor-only. */
/* No JavaScript — a 2px translateY lift + soft shadow on hover, and a :has()-driven chevron nudge toward the next crumb, all spring-eased and compositor-only. */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: Modern Micro-Interaction Animated Hover Breadcrumbs
Source: https://codefronts.com/navigation/css-breadcrumbs/scale-peek/
A breadcrumb built around a delightful hover micro-interaction: each crumb lifts with a subtle translateY, gains a soft shadow, and its separator chevron nudges forward — small, spring-eased motions that make the trail feel responsive and alive without being distracting. Compositor-only and reduced-motion-safe.
## HTML
```html
<nav class="bc-39" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Hardware</a></li>
<li><a href="#" aria-current="page">Keyboards</a></li>
</ol>
</nav>
```
## CSS
```css
.bc-39 {
display: grid;
width: 100%;
min-height: 100vh;
--accent: oklch(0.58 0.15 285);
font-family: system-ui,'Segoe UI',sans-serif;
padding: 34px 30px;
background: oklch(0.98 0.004 285);
place-items: center;
}
.bc-39 * {
box-sizing: border-box;
}
.bc-39 ol {
display: flex;
flex-wrap: wrap;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
}
.bc-39 li {
display: flex;
align-items: center;
}
.bc-39 li:not(:last-child)::after {
content: "›";
margin: 0 8px;
color: oklch(0.7 0.02 285);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),color .18s ease;
}
.bc-39 li:has(a:hover):not(:last-child)::after {
transform: translateX(3px);
color: var(--accent);
}
.bc-39 a {
display: inline-block;
padding: 7px 12px;
border-radius: 9px;
font: 600 13.5px/1 system-ui,sans-serif;
color: oklch(0.45 0.04 285);
text-decoration: none;
background: transparent;
box-shadow: 0 0 0 rgba(40,20,80,0);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,color .18s ease,background-color .18s ease;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: translateY(-2px);
color: var(--accent);
background: #fff;
box-shadow: 0 6px 16px rgba(40,20,80,.14);
outline: none;
}
.bc-39 a:focus-visible {
box-shadow: 0 6px 16px rgba(40,20,80,.14),0 0 0 2px var(--accent);
}
.bc-39 li[aria-current="page"] a {
color: oklch(0.3 0.02 285);
font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
.bc-39 a,
.bc-39 li::after {
transition: none;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: none;
}
}
```
## JavaScript
```js
/* No JavaScript — a 2px translateY lift + soft shadow on hover, and a :has()-driven chevron nudge toward the next crumb, all spring-eased and compositor-only. */
```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: Modern Micro-Interaction Animated Hover Breadcrumbs
Source: https://codefronts.com/navigation/css-breadcrumbs/scale-peek/
A breadcrumb built around a delightful hover micro-interaction: each crumb lifts with a subtle translateY, gains a soft shadow, and its separator chevron nudges forward — small, spring-eased motions that make the trail feel responsive and alive without being distracting. Compositor-only and reduced-motion-safe.
## HTML
```html
<nav class="bc-39" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Hardware</a></li>
<li><a href="#" aria-current="page">Keyboards</a></li>
</ol>
</nav>
```
## CSS
```css
.bc-39 {
display: grid;
width: 100%;
min-height: 100vh;
--accent: oklch(0.58 0.15 285);
font-family: system-ui,'Segoe UI',sans-serif;
padding: 34px 30px;
background: oklch(0.98 0.004 285);
place-items: center;
}
.bc-39 * {
box-sizing: border-box;
}
.bc-39 ol {
display: flex;
flex-wrap: wrap;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
}
.bc-39 li {
display: flex;
align-items: center;
}
.bc-39 li:not(:last-child)::after {
content: "›";
margin: 0 8px;
color: oklch(0.7 0.02 285);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),color .18s ease;
}
.bc-39 li:has(a:hover):not(:last-child)::after {
transform: translateX(3px);
color: var(--accent);
}
.bc-39 a {
display: inline-block;
padding: 7px 12px;
border-radius: 9px;
font: 600 13.5px/1 system-ui,sans-serif;
color: oklch(0.45 0.04 285);
text-decoration: none;
background: transparent;
box-shadow: 0 0 0 rgba(40,20,80,0);
transition: transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,color .18s ease,background-color .18s ease;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: translateY(-2px);
color: var(--accent);
background: #fff;
box-shadow: 0 6px 16px rgba(40,20,80,.14);
outline: none;
}
.bc-39 a:focus-visible {
box-shadow: 0 6px 16px rgba(40,20,80,.14),0 0 0 2px var(--accent);
}
.bc-39 li[aria-current="page"] a {
color: oklch(0.3 0.02 285);
font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
.bc-39 a,
.bc-39 li::after {
transition: none;
}
.bc-39 a:hover,
.bc-39 a:focus-visible {
transform: none;
}
}
```
## JavaScript
```js
/* No JavaScript — a 2px translateY lift + soft shadow on hover, and a :has()-driven chevron nudge toward the next crumb, all spring-eased and compositor-only. */
```How this works
The interaction stacks three tiny motions on :hover/:focus-visible, all compositor-friendly: the crumb rises with transform:translateY(-2px), a soft box-shadow fades in beneath it (depth cue), and — the detail that sells it — the following separator chevron nudges forward via :has() (li:has(+ li a:hover)::after or the sibling chevron shifts) to imply momentum toward the next level. Each uses a gentle spring-like cubic-bezier so the motion has a little settle rather than a linear slide.
The philosophy is restraint: one well-orchestrated 2px lift with a shadow reads as 'this is interactive and pleasant' far better than large or numerous animations. Durations sit around 180–220ms — fast enough to feel immediate, slow enough to perceive. Everything is transform/opacity/box-shadow, so there's no layout thrash, and prefers-reduced-motion strips the movement to a simple colour change for users who opt out.
Make it yours
- Lift height + shadow depth are the two dials — 2px/soft is subtle; 4px/deeper is playful.
- Easing personality: a slight overshoot cubic-bezier adds bounce; a smooth ease reads more corporate.
- Add an icon that rotates or slides on hover for an extra layer — keep total motion small.
- Chevron nudge distance (2–3px) is the momentum cue; drop it for a calmer trail.
Gotchas — read before shipping
- Animate only
transform/opacity/box-shadow— nevertop/margin, which force layout every frame. - Keep motion small and fast — big or slow hover animations feel laggy and draw the eye away from content.
:has()-driven neighbour effects need the modern selector; provide the plain hover as the base so it degrades to a still-nice lift.- Always ship the reduced-motion fallback — vestibular-sensitive users need the option to drop the movement.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 121+ | 111+ |
Floor set by :has(), oklch() as this demo is written. The core technique itself goes back further — Chrome 105+.
:has() (2023) drives the neighbour-chevron nudge; the core lift+shadow works everywhere. Degrades to a plain hover lift without :has().