Detent web components
By Shane Logsdon

Media & effects

Depth Corridor

Implemented

CSS 3D perspective tunnel with progressive depth blur

detent-depth-corridor

Port status
Catalog status
implemented
Origin
upstream
Phase
4
Wave
phase-4-cinematic
Group
Media & effects

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

Variants

A 3D perspective corridor: cards recede along translateZ with a progressive focal blur, and the active layer springs forward with the pinned springSmooth physics (stiffness 200, damping 28, mass 0.85). Use the arrow keys, the arrow buttons or the indicator pills — each change dispatches one detent-layer-change with detail { index }. Moving the pointer over a corridor adds the parallax tilt.

no layer change yet

Six layers with the third active: only the three nearest layers on each side are in the DOM, so the window is seven cards at most. The status line below each corridor is driven by the documented event.

no layer change yet

With prefers-reduced-motion: reduce the springs are not created at all: the layer change is applied immediately, the parallax is off and the focal blur is removed. The layer body text is always text — the component never parses markup.

Recorded states

  • reduced motion recorded state; no setter

Usage

Import

import '/components/depth-corridor/define.js';

// or register it yourself
import { DetentDepthCorridor } from '/components/depth-corridor/depth-corridor.js';

Attributes

  • active-layer-index
  • enable-parallax
  • perspective-depth

Properties

  • layers
  • activeLayerIndex
  • enableParallax
  • perspectiveDepth
  • onLayerChange

Slots

No slots are recorded for this component.

Events

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

Example

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

<detent-depth-corridor
  active-layer-index="0"
  enable-parallax
  perspective-depth="1000"
></detent-depth-corridor>

Property-only values

const element = document.querySelector('detent-depth-corridor');
if (element !== null) {
  // layers: DepthLayer[]
  element.layers = [];
  // onLayerChange: (index: number) => void
  element.onLayerChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/DepthCorridor.tsx sha256 d794f0b39eda…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
active-layer-indexactiveLayerIndexnumber0Kebab-case attribute; camelCase property.
enable-parallaxenableParallaxbooleantruePresence semantics: present means true, absent means false.
perspective-depthperspectiveDepthnumber1000Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
layersDepthLayer[]—Property-only: assign from script; not accepted as an attribute.
activeLayerIndexnumber0Property-only: assign from script; not accepted as an attribute.
enableParallaxbooleantrueProperty-only: assign from script; not accepted as an attribute.
perspectiveDepthnumber1000Property-only: assign from script; not accepted as an attribute.
onLayerChange(index: number) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

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

Slots

No slots are recorded for this component.

CSS customisation

  • 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.
  • `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.
  • Layer content is text: the row records no slots.
  • blurAmount is declared upstream but never read.
  • Opacity is continuous in the interpolated offset; upstream switched formula at the current layer and jumped by 0.25.
  • There is no controlled/uncontrolled split: the attribute is authoritative and reflected as a clamped value.
  • A clamped no-op emits nothing.
  • Reduced motion is implemented even though the pinned source never read the preference.
  • Pills and arrows are real buttons with aria-current and accessible names.
  • The horizontal depth step shrinks below the narrowest recorded viewport so the stack never overflows.
  • lucide-react chevrons become local inline SVGs (the ISC notice is retained in THIRD_PARTY_NOTICES.md).
  • Framer springSmooth becomes the shared spring helper at the same 200/28/0.85 physics.

What it does

A CSS 3D perspective corridor: layered cards recede along a translateZ depth axis with a progressive focal blur and a spring transition, plus pointer parallax and arrow-key navigation.

Upstream behaviour
  • True 3D perspective translation (translateZ) and multi-layered receding depth geometry
  • Progressive depth-of-field focal blurring and subtle mouse tracking parallax
  • Keyboard arrow navigation (Left/Right/Up/Down) with smooth spring transitions
  • Tactile indicator pills and interactive layer selection
Differences from upstream
  • 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.
  • `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.
  • Layer content is text: the row records no slots.
  • blurAmount is declared upstream but never read.
  • Opacity is continuous in the interpolated offset; upstream switched formula at the current layer and jumped by 0.25.
  • There is no controlled/uncontrolled split: the attribute is authoritative and reflected as a clamped value.
  • A clamped no-op emits nothing.
  • Reduced motion is implemented even though the pinned source never read the preference.
  • Pills and arrows are real buttons with aria-current and accessible names.
  • The horizontal depth step shrinks below the narrowest recorded viewport so the stack never overflows.
  • lucide-react chevrons become local inline SVGs (the ISC notice is retained in THIRD_PARTY_NOTICES.md).
  • Framer springSmooth becomes the shared spring helper at the same 200/28/0.85 physics.
Previous: Dependency Trace Next: Tooltip

Docs

Components

Project