Detent web components
By Shane Logsdon

Navigation

Breadcrumb

Implemented

Gliding hover pill with animated trail expansion

detent-breadcrumb

Port status
Catalog status
implemented
Origin
upstream
Phase
2
Wave
phase-2-navigation
Group
Navigation

5.4 KB gzip for the class module · inlines the shared spring

Variants

Recorded states

  • hover recorded state; no setter
  • collapsed recorded state; no setter

Usage

Import

import '/components/breadcrumb/define.js';

// or register it yourself
import { DetentBreadcrumb } from '/components/breadcrumb/breadcrumb.js';

Attributes

  • max-items
  • separator

Properties

  • items
  • separator
  • maxItems
  • as
  • expanded

Slots

No slots are recorded for this component.

Events

No events are recorded for this component.

Example

Runnable example: site/examples/breadcrumb.ts. The snippet below is generated from the catalogued attributes.

<detent-breadcrumb
  max-items="4"
  separator="chevron"
></detent-breadcrumb>

Property-only values

const element = document.querySelector('detent-breadcrumb');
if (element !== null) {
  // items: BreadcrumbItem[]
  element.items = [];
  // as: React.ElementType
  element.as = 'a';
}

Source

The pinned upstream source for this row:

  • src/components/ui/breadcrumb.tsx sha256 585c49067eba…

Native files

The native module and its artifacts:

  • src/components/breadcrumb/breadcrumb.ts
  • src/components/breadcrumb/styles.css
  • src/components/breadcrumb/define.ts
  • docs/components/breadcrumb.md
  • site/examples/breadcrumb.ts
  • tests/browser/breadcrumb.spec.ts
  • evidence/components/breadcrumb.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
max-itemsmaxItemsnumber4Kebab-case attribute; camelCase property.
separatorseparator'chevron' | 'slash''chevron'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
itemsBreadcrumbItem[]undefinedProperty-only: assign from script; not accepted as an attribute.
separator'chevron' | 'slash''chevron'Property-only: assign from script; not accepted as an attribute.
maxItemsnumber4Property-only: assign from script; not accepted as an attribute.
asReact.ElementType'a'Property-only: assign from script; not accepted as an attribute.
expanded——Recorded in the catalog without a top-level type; pinned from source when the row is ported.

Events

No custom events are recorded for this component.

Slots

No slots are recorded for this component.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • `as` accepts a tag name ('a' default, e.g. 'my-router-link') - a React element type or constructor has no native equivalent.
  • `maxItems` is clamped to >= 2, unparsable becomes 4, and the read-only `maxItems` getter reports the resolved value.
  • The hover pill uses the upstream source's code-local spring (500/40/0.6) through the shared spring helper, not one of the recorded motionTokens.
  • `ChevronRight`/`Ellipsis` become a local decorative SVG and a text glyph in a labelled button; the reveal is a CSS keyframe removed under reduced motion.
  • Upstream's hard-coded neutral-* pairs become --detent-color-* and the separator keeps 3:1+ (accessibility correction); an `items` write collapses the trail again.
  • `part="link"` also marks a crumb that is not a link (`<span data-static="true">`); only items with `href` render an `<a>`/`as` element.

What it does

A navigation trail with a gliding hover pill, an ellipsis trigger that expands a collapsed middle with focus transfer, and chevron or slash separators.

Upstream behaviour
  • Hover pill smoothly glides between links using Framer Motion layoutId
  • Long trails collapse the middle items into an ellipsis trigger button
  • Clicking ellipsis animates hidden crumbs in while focus transfers to the first revealed crumb
  • Supports chevron or slash separator styles and custom router link components (Next.js Link or <a>)
  • Full accessibility with aria-label="Breadcrumb", aria-current="page", and keyboard navigation
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • `as` accepts a tag name ('a' default, e.g. 'my-router-link') - a React element type or constructor has no native equivalent.
  • `maxItems` is clamped to >= 2, unparsable becomes 4, and the read-only `maxItems` getter reports the resolved value.
  • The hover pill uses the upstream source's code-local spring (500/40/0.6) through the shared spring helper, not one of the recorded motionTokens.
  • `ChevronRight`/`Ellipsis` become a local decorative SVG and a text glyph in a labelled button; the reveal is a CSS keyframe removed under reduced motion.
  • Upstream's hard-coded neutral-* pairs become --detent-color-* and the separator keeps 3:1+ (accessibility correction); an `items` write collapses the trail again.
  • `part="link"` also marks a crumb that is not a link (`<span data-static="true">`); only items with `href` render an `<a>`/`as` element.
Previous: Branching Submenu Next: Button

Docs

Components

Project