You Might Also Like
Breadcrumbs — Collapsing Trail HTML CSS JS Snippet
Breadcrumb Trail with Collapsing Middle Items · Navigation · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Breadcrumb Trail — aria-current Navigation with Collapsing Middle Segments

Breadcrumbs show a user where they are in a site's hierarchy and let them jump back up to any ancestor level in one click — standard on any site with nested categories, file paths, or multi-step settings. This snippet covers both the simple three-level case and the harder problem: what happens when the path is eight levels deep and doesn't fit on one line.
Semantic structure
The trail is a <nav aria-label="Breadcrumb"> wrapping an <ol> — an ordered list, not an unordered one, because a breadcrumb path has a meaningful sequence. Every visited ancestor is a real <a href="...">, and only the final, current segment is a plain <span> with aria-current="page" — marking the current location as non-interactive is correct because linking a page to itself serves no purpose and aria-current tells assistive technology which segment represents "here" without needing to infer it from position.
Separators as siblings, not pseudo-elements
Each / separator is a real <span class="sep"> element placed before its link, rather than a ::before CSS pseudo-element. This is a deliberate accessibility choice: CSS-generated content is inconsistently exposed to screen readers across browsers, so a visible, real DOM separator guarantees consistent behavior, at the minor cost of one extra element per crumb.
The collapsing problem
A breadcrumb trail with eight segments doesn't fit in a typical sidebar or mobile viewport width. This snippet's renderLong() checks window.innerWidth < 480 && LONG_PATH.length > 4 and, when both are true, renders only the first segment, a … button standing in for the hidden middle, and the last two segments — the same convention Windows Explorer and macOS Finder's breadcrumb bars use. The first segment stays because it anchors the user to the top of the hierarchy; the last two stay because they show exactly where the user currently is and the level directly above it.
The collapse button is interactive, not decorative
The … is a real <button>, not static text, with aria-label="Show hidden path segments" — clicking it calls expandCrumbs(), which re-renders the full uncollapsed list. A common mistake is rendering the ellipsis as plain text, which discards the ability to ever see the hidden segments; making it a button preserves full navigability while still solving the width problem.
Recalculating on resize
window.addEventListener('resize', renderLong) means resizing the browser (or rotating a device) re-evaluates the collapse condition and re-renders — a trail collapsed on a narrow mobile viewport automatically expands back to full when the window widens past the 480px threshold, without requiring a page reload.
Building the HTML string safely
crumbHtml() is the single function responsible for rendering one segment, called identically whether building the collapsed or expanded view — this avoids duplicating the link-vs-current-span branching logic in two places, which is a common source of the two views silently drifting apart from each other over time.
Truncating long individual labels
This snippet handles too many *segments*; a segment with a very long individual label (a long file name, for instance) is a separate problem, best solved with text-overflow: ellipsis and a max-width on that specific <a> or <span>, combined with a native title attribute so the full text is still available on hover.
React integration
In React, pass the path as an array of { label, href } objects, derive the collapsed/expanded view with a small useMemo keyed on a window.innerWidth value tracked via a resize listener in useEffect, and render the last item as a <span aria-current="page"> exactly as this snippet does.
See also the breadcrumb dropdown for a variant where each segment itself opens a dropdown of sibling pages, and the table of contents snippet for a different kind of "where am I" navigation aid.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this breadcrumb trail's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why the current page renders as a plain span with aria-current instead of a link, and how the collapse-to-ellipsis logic decides which segments to keep versus hide on a narrow viewport. Since this snippet builds its path from a hardcoded array, it's a good candidate to ask the assistant to wire up to your actual router — describe your routing library and have it generate the segment array (with real hrefs and human-readable labels) from the current URL automatically. Beyond that, ask it to add a dropdown on the collapse button showing the hidden segments as a menu instead of fully expanding the trail, or to add individual-segment text truncation for very long labels.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an accessible breadcrumb trail component in plain HTML, CSS, and JavaScript, no framework, no libraries.
Requirements:
- Use a semantic nav element labeled as a breadcrumb, wrapping an ordered list where each list item represents one level of a page hierarchy. Every ancestor level must be a real link; the final, current-page segment must be plain non-interactive text marked with an aria-current attribute indicating it is the current page, not a link to itself.
- Render separators between segments as real visible DOM elements rather than CSS-generated pseudo-element content.
- Given a path with more than four segments, automatically collapse the middle segments into a single interactive ellipsis button when the viewport is narrow (for example under 480px wide), showing only the first segment, the collapse button, and the last two segments. On a wider viewport, render the full uncollapsed path instead.
- The collapse ellipsis must be a real button (not static punctuation) with an appropriate accessible label, and clicking it must re-render the full, uncollapsed path in place.
- Recalculate whether to collapse or expand whenever the browser window is resized, so a trail collapsed on a narrow width automatically expands again if the window is widened past the threshold, without requiring a page reload.
- Use one shared rendering function for building both the collapsed and the fully expanded views so the logic for "is this the last segment" and "is this a link or the current-page text" is not duplicated in two separate code paths.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Copy the nav/ol structureUse <nav aria-label="Breadcrumb"><ol>...</ol></nav> with one <li> per path segment — links for ancestors, a plain aria-current span for the current page.
- 2Replace the LONG_PATH arraySwap LONG_PATH for your real route hierarchy, either hardcoded per page or generated from your router's current path segments.
- 3Add the CSSPaste the CSS once — it styles both the short static example and the dynamically rendered long trail identically.
- 4Adjust the collapse thresholdChange the 480px width check and the "> 4 segments" condition in renderLong() to match when your layout actually needs to collapse.
- 5Wire real hrefsReplace the demo's href="#" placeholders with real ancestor URLs so each breadcrumb link actually navigates.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
A link to the page the user is already on serves no purpose and can confuse screen reader users into thinking it navigates somewhere new. Using a plain span with aria-current="page" correctly marks it as the non-interactive current location.
Edit the condition in renderLong() — currently window.innerWidth < 480 && LONG_PATH.length > 4 — and the slice indices in the collapsed branch (head = first 1, tail = last 2) to whatever combination fits your layout.
CSS-generated content is not consistently exposed to assistive technology across all browsers and screen readers. A real <span class="sep"> guarantees the separator (or its absence, since it can be hidden from screen readers with aria-hidden if desired) behaves predictably everywhere.
Split your current route pathname on "/", map each segment to a { label, href } pair by accumulating the path so far for each href, and optionally translate URL slugs into human-readable labels via a lookup table or your CMS data.
Open the Export menu on the snippet page for a React component accepting a path array as a prop with the same collapse-on-resize logic in a useEffect, a React + Tailwind version, a Vue 3 SFC, or an Angular standalone component with an @Input() path array.