39 CSS Breadcrumbs23 / 39
Modern Pure CSS Arrow Breadcrumb Bar using clip-path Polygons
The classic interlocking-arrow breadcrumb — no images, no background sprites, no border hacks. Each crumb is a single anchor sliced into a chevron with one clip-path: polygon(), and a --arrow-depth custom property tunes the point angle across the whole trail from one line. First and last crumbs get clean flat outer edges.
Published Updated
The code
<nav class="bc-23" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Electronics</a></li>
<li><a href="#">Audio</a></li>
<li><a href="#">Headphones</a></li>
<li><a href="#" aria-current="page">Wireless Over-Ear</a></li>
</ol>
</nav><nav class="bc-23" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Electronics</a></li>
<li><a href="#">Audio</a></li>
<li><a href="#">Headphones</a></li>
<li><a href="#" aria-current="page">Wireless Over-Ear</a></li>
</ol>
</nav>.bc-23 {
width: 100%;
min-height: 100vh;
--arrow-depth: 16px;
--h: 40px;
font-family: system-ui,'Segoe UI',sans-serif;
padding: 28px;
background: oklch(0.96 0.005 260);
display: flex;
align-items: center;
justify-content: center;
}
.bc-23 * {
box-sizing: border-box;
}
.bc-23 ol {
display: flex;
list-style: none;
margin: 0;
padding: 0;
filter: drop-shadow(0 2px 4px oklch(0 0 0/.12));
}
.bc-23 li {
display: flex;
}
.bc-23 a {
display: flex;
align-items: center;
height: var(--h);
padding: 0 18px 0 26px;
margin-inline-start: calc(var(--arrow-depth) * -1);
text-decoration: none;
font: 600 13.5px/1 system-ui,sans-serif;
color: #fff;
background: oklch(0.6 0.02 260);
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%,var(--arrow-depth) 50%);
transition: filter .18s ease,background-color .18s ease;
}
.bc-23 li:nth-child(2) a {
background: oklch(0.56 0.03 260);
}
.bc-23 li:nth-child(3) a {
background: oklch(0.52 0.04 260);
}
.bc-23 li:nth-child(4) a {
background: oklch(0.48 0.05 260);
}
.bc-23 li:first-child a {
padding-left: 20px;
margin-inline-start: 0;
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%);
}
.bc-23 li:last-child a {
padding-right: 22px;
background: oklch(0.62 0.17 265);
color: #fff;
clip-path: polygon(0 0,100% 0,100% 100%,0 100%,var(--arrow-depth) 50%);
}
.bc-23 a:hover {
filter: brightness(1.12);
}
.bc-23 a:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2.5px #fff,inset 0 0 0 4px oklch(0.62 0.17 265);
}
.bc-23 li:last-child a[aria-current="page"] {
cursor: default;
}
@media (prefers-reduced-motion: reduce) {
.bc-23 a {
transition: none;
}
}.bc-23 {
width: 100%;
min-height: 100vh;
--arrow-depth: 16px;
--h: 40px;
font-family: system-ui,'Segoe UI',sans-serif;
padding: 28px;
background: oklch(0.96 0.005 260);
display: flex;
align-items: center;
justify-content: center;
}
.bc-23 * {
box-sizing: border-box;
}
.bc-23 ol {
display: flex;
list-style: none;
margin: 0;
padding: 0;
filter: drop-shadow(0 2px 4px oklch(0 0 0/.12));
}
.bc-23 li {
display: flex;
}
.bc-23 a {
display: flex;
align-items: center;
height: var(--h);
padding: 0 18px 0 26px;
margin-inline-start: calc(var(--arrow-depth) * -1);
text-decoration: none;
font: 600 13.5px/1 system-ui,sans-serif;
color: #fff;
background: oklch(0.6 0.02 260);
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%,var(--arrow-depth) 50%);
transition: filter .18s ease,background-color .18s ease;
}
.bc-23 li:nth-child(2) a {
background: oklch(0.56 0.03 260);
}
.bc-23 li:nth-child(3) a {
background: oklch(0.52 0.04 260);
}
.bc-23 li:nth-child(4) a {
background: oklch(0.48 0.05 260);
}
.bc-23 li:first-child a {
padding-left: 20px;
margin-inline-start: 0;
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%);
}
.bc-23 li:last-child a {
padding-right: 22px;
background: oklch(0.62 0.17 265);
color: #fff;
clip-path: polygon(0 0,100% 0,100% 100%,0 100%,var(--arrow-depth) 50%);
}
.bc-23 a:hover {
filter: brightness(1.12);
}
.bc-23 a:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2.5px #fff,inset 0 0 0 4px oklch(0.62 0.17 265);
}
.bc-23 li:last-child a[aria-current="page"] {
cursor: default;
}
@media (prefers-reduced-motion: reduce) {
.bc-23 a {
transition: none;
}
}/* No JavaScript — each crumb is one <a> sliced by clip-path: polygon(); --arrow-depth drives both the chevron math and the overlap margin. */
/* No JavaScript — each crumb is one <a> sliced by clip-path: polygon(); --arrow-depth drives both the chevron math and the overlap margin. */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 Pure CSS Arrow Breadcrumb Bar using clip-path Polygons
Source: https://codefronts.com/navigation/css-breadcrumbs/arrow-chain/
The classic interlocking-arrow breadcrumb — no images, no background sprites, no border hacks. Each crumb is a single anchor sliced into a chevron with one clip-path: polygon(), and a --arrow-depth custom property tunes the point angle across the whole trail from one line. First and last crumbs get clean flat outer edges.
## HTML
```html
<nav class="bc-23" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Electronics</a></li>
<li><a href="#">Audio</a></li>
<li><a href="#">Headphones</a></li>
<li><a href="#" aria-current="page">Wireless Over-Ear</a></li>
</ol>
</nav>
```
## CSS
```css
.bc-23 {
width: 100%;
min-height: 100vh;
--arrow-depth: 16px;
--h: 40px;
font-family: system-ui,'Segoe UI',sans-serif;
padding: 28px;
background: oklch(0.96 0.005 260);
display: flex;
align-items: center;
justify-content: center;
}
.bc-23 * {
box-sizing: border-box;
}
.bc-23 ol {
display: flex;
list-style: none;
margin: 0;
padding: 0;
filter: drop-shadow(0 2px 4px oklch(0 0 0/.12));
}
.bc-23 li {
display: flex;
}
.bc-23 a {
display: flex;
align-items: center;
height: var(--h);
padding: 0 18px 0 26px;
margin-inline-start: calc(var(--arrow-depth) * -1);
text-decoration: none;
font: 600 13.5px/1 system-ui,sans-serif;
color: #fff;
background: oklch(0.6 0.02 260);
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%,var(--arrow-depth) 50%);
transition: filter .18s ease,background-color .18s ease;
}
.bc-23 li:nth-child(2) a {
background: oklch(0.56 0.03 260);
}
.bc-23 li:nth-child(3) a {
background: oklch(0.52 0.04 260);
}
.bc-23 li:nth-child(4) a {
background: oklch(0.48 0.05 260);
}
.bc-23 li:first-child a {
padding-left: 20px;
margin-inline-start: 0;
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%);
}
.bc-23 li:last-child a {
padding-right: 22px;
background: oklch(0.62 0.17 265);
color: #fff;
clip-path: polygon(0 0,100% 0,100% 100%,0 100%,var(--arrow-depth) 50%);
}
.bc-23 a:hover {
filter: brightness(1.12);
}
.bc-23 a:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2.5px #fff,inset 0 0 0 4px oklch(0.62 0.17 265);
}
.bc-23 li:last-child a[aria-current="page"] {
cursor: default;
}
@media (prefers-reduced-motion: reduce) {
.bc-23 a {
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — each crumb is one <a> sliced by clip-path: polygon(); --arrow-depth drives both the chevron math and the overlap margin. */
```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 Pure CSS Arrow Breadcrumb Bar using clip-path Polygons
Source: https://codefronts.com/navigation/css-breadcrumbs/arrow-chain/
The classic interlocking-arrow breadcrumb — no images, no background sprites, no border hacks. Each crumb is a single anchor sliced into a chevron with one clip-path: polygon(), and a --arrow-depth custom property tunes the point angle across the whole trail from one line. First and last crumbs get clean flat outer edges.
## HTML
```html
<nav class="bc-23" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Electronics</a></li>
<li><a href="#">Audio</a></li>
<li><a href="#">Headphones</a></li>
<li><a href="#" aria-current="page">Wireless Over-Ear</a></li>
</ol>
</nav>
```
## CSS
```css
.bc-23 {
width: 100%;
min-height: 100vh;
--arrow-depth: 16px;
--h: 40px;
font-family: system-ui,'Segoe UI',sans-serif;
padding: 28px;
background: oklch(0.96 0.005 260);
display: flex;
align-items: center;
justify-content: center;
}
.bc-23 * {
box-sizing: border-box;
}
.bc-23 ol {
display: flex;
list-style: none;
margin: 0;
padding: 0;
filter: drop-shadow(0 2px 4px oklch(0 0 0/.12));
}
.bc-23 li {
display: flex;
}
.bc-23 a {
display: flex;
align-items: center;
height: var(--h);
padding: 0 18px 0 26px;
margin-inline-start: calc(var(--arrow-depth) * -1);
text-decoration: none;
font: 600 13.5px/1 system-ui,sans-serif;
color: #fff;
background: oklch(0.6 0.02 260);
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%,var(--arrow-depth) 50%);
transition: filter .18s ease,background-color .18s ease;
}
.bc-23 li:nth-child(2) a {
background: oklch(0.56 0.03 260);
}
.bc-23 li:nth-child(3) a {
background: oklch(0.52 0.04 260);
}
.bc-23 li:nth-child(4) a {
background: oklch(0.48 0.05 260);
}
.bc-23 li:first-child a {
padding-left: 20px;
margin-inline-start: 0;
clip-path: polygon(0 0,calc(100% - var(--arrow-depth)) 0,100% 50%,calc(100% - var(--arrow-depth)) 100%,0 100%);
}
.bc-23 li:last-child a {
padding-right: 22px;
background: oklch(0.62 0.17 265);
color: #fff;
clip-path: polygon(0 0,100% 0,100% 100%,0 100%,var(--arrow-depth) 50%);
}
.bc-23 a:hover {
filter: brightness(1.12);
}
.bc-23 a:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2.5px #fff,inset 0 0 0 4px oklch(0.62 0.17 265);
}
.bc-23 li:last-child a[aria-current="page"] {
cursor: default;
}
@media (prefers-reduced-motion: reduce) {
.bc-23 a {
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — each crumb is one <a> sliced by clip-path: polygon(); --arrow-depth drives both the chevron math and the overlap margin. */
```How this works
Every crumb is an <a> clipped by clip-path: polygon(0 0, calc(100% - var(--arrow-depth)) 0, 100% 50%, calc(100% - var(--arrow-depth)) 100%, 0 100%, var(--arrow-depth) 50%) — a six-point shape that pushes a point out of the right edge and bites the same wedge out of the left edge, so consecutive crumbs interlock. A negative margin-inline-start equal to the arrow depth slides each crumb into the notch of the one before it, and gap is intentionally avoided because the shapes must overlap.
The arrow angle is a single custom property: --arrow-depth:16px on the list drives both the clip math and the overlap margin, so re-tuning the whole trail's chevron is one edit. The first crumb overrides its polygon to a flat left edge (it has nothing to interlock with) and the last (current page) overrides to a flat right edge, giving the bar clean bookends. Colour steps down the trail with color-mix() so depth reads as tonal recession, and the current crumb takes the accent fill.
Make it yours
--arrow-depthis the whole API: 10px for a soft nudge, 22px for an aggressive dart — the overlap margin tracks it automatically.- Recolour the trail by editing the two oklch stops in the
color-mix()ladder; the accent on the current page is one variable. - Add a home icon by dropping an inline SVG before the first crumb's text — the flat-edged first crumb has room for it.
- For RTL sites the logical
margin-inline-startand flipped polygon just work — swap the two calc() sides if you want the point to face left.
Gotchas — read before shipping
clip-pathclips overflow AND the focus outline — put the:focus-visiblering on an insetbox-shadowinstead ofoutline, or it gets sliced off at the chevron.- Overlapping crumbs mean later crumbs paint over earlier ones; set
position:relative;z-indexascending if a hover lift must sit above its neighbour. - Don't put the separators in text — the arrow IS the separator, so the accessible name of each link stays clean for screen readers.
- Very long labels distort the point; cap with
max-width+ ellipsis if your CMS can emit long titles.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 113+ | 111+ |
Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 55+.
clip-path: polygon() is ancient in web terms — universal for a decade. oklch()/color-mix() (2023) only style the palette; swap to hsl() for older engines.