Detent web components
By Shane Logsdon

Navigation

Stepper

Implemented

Animated connector line, gliding focus ring, and step panels

detent-stepper

Port status
Catalog status
implemented
Origin
upstream
Phase
2
Wave
phase-2-navigation
Group
Navigation
Account details and the workspace name.
Profile and notification preferences.
Plan and payment method.
Everything is ready.

6.7 KB gzip for the class module

Variants

Account details and the workspace name.
Profile and notification preferences.
Plan and payment method.
Everything is ready.
Account details and the workspace name.
Profile and notification preferences.
Plan and payment method.
Everything is ready.

Recorded states

  • active recorded state; no setter
  • completed recorded state; no setter

Usage

Import

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

// or register it yourself
import { DetentStepper } from '/components/stepper/stepper.js';

Attributes

  • current
  • orientation

Properties

  • steps
  • current
  • orientation
  • onStepChange

Slots

  • default

Events

  • detent-step-change — { index: number }

Example

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

<detent-stepper
  current="0"
  orientation="horizontal"
></detent-stepper>

Property-only values

const element = document.querySelector('detent-stepper');
if (element !== null) {
  // steps: Step[]
  element.steps = [];
  // onStepChange: (index: number) => void
  element.onStepChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/stepper.tsx sha256 ec717f06b1b2…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
currentcurrentnumber0Kebab-case attribute; camelCase property.
orientationorientation'horizontal' | 'vertical''horizontal'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
stepsStep[]undefinedProperty-only: assign from script; not accepted as an attribute.
currentnumber0Property-only: assign from script; not accepted as an attribute.
orientation'horizontal' | 'vertical''horizontal'Property-only: assign from script; not accepted as an attribute.
onStepChange(index: number) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-step-changeonStepChange(index: number) => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • `StepPanels` folds into `detent-stepper` as the `default` slot contract and is published as a TYPE, not a second tag (the row records one tag and one slot, PC-13.1).
  • Completed steps are always buttons (upstream only when `onStepChange` was passed, which is undetectable natively); activating one moves focus to that step's static body instead of dropping focus on document body (PC-5.1).
  • The ring glides with the upstream code-local spring (420/34/0.8) via the shared helper, not a recorded token; the connector fill keeps upstream fixed timing (350ms) and the panel slide 180ms.
  • The completed check is static (upstream path-draw is decorative); `className` maps to parts and `React.Children` to the `default` slot.
  • `current` is reflected, and a programmatic write animates like a user activation without emitting an event.
  • Upstream neutral-* pairs become --detent-color-*; inactive steps are muted but never faded.

What it does

A progress trail with a connector that fills as you advance, a single ring that glides to the active step, clickable completed steps, and a direction-aware panel slide for slotted content.

Upstream behaviour
  • Connector line fills smoothly as you advance through steps
  • A single active indicator ring glides between steps using Framer Motion layoutId
  • Completed step checkmark animates its stroke path on completion
  • Direction-aware StepPanels: slides left/right depending on forward or backward navigation
  • Horizontal and vertical layouts, clickable completed steps, and responsive mobile labels
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • `StepPanels` folds into `detent-stepper` as the `default` slot contract and is published as a TYPE, not a second tag (the row records one tag and one slot, PC-13.1).
  • Completed steps are always buttons (upstream only when `onStepChange` was passed, which is undetectable natively); activating one moves focus to that step's static body instead of dropping focus on document body (PC-5.1).
  • The ring glides with the upstream code-local spring (420/34/0.8) via the shared helper, not a recorded token; the connector fill keeps upstream fixed timing (350ms) and the panel slide 180ms.
  • The completed check is static (upstream path-draw is decorative); `className` maps to parts and `React.Children` to the `default` slot.
  • `current` is reflected, and a programmatic write animates like a user activation without emitting an event.
  • Upstream neutral-* pairs become --detent-color-*; inactive steps are muted but never faded.
Previous: Stat Card Next: Sticky Pages

Docs

Components

Project