Detent web components
By Shane Logsdon

Media & effects

Car Smoke Page Transition

Implemented

Cinematic automotive page transition

detent-car-smoke-page-transition

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

8.4 KB gzip for the class module

Variants

Start journey

The trigger is the real light-DOM button above, so Enter and Space work with native semantics and the button keeps its own name. Activating it runs the pinned choreography: the car crosses for car-duration (700 ms) with body pitch, roll and wheel spin, a canvas paints drift smoke, skid marks and speed lines, the wipe fades in at car-duration × 0.65, and detent-transition-complete fires at car-delay + page-transition-delay + 100 ms — the point at which a consumer navigates. The overlay is fixed, hidden and inert at rest, and it owns the pointer while it runs. With prefers-reduced-motion: reduce no car, wheel or canvas animation runs, but the same event fires and the same final state is reached. This demo never navigates.

Note: reduced-motion path Travel right-to-left
No transition yet.

Recorded states

  • dark theme recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/car-smoke-page-transition/define.js';

// or register it yourself
import { DetentCarSmokePageTransition } from '/components/car-smoke-page-transition/car-smoke-page-transition.js';

Attributes

  • car-delay
  • car-duration
  • car-size
  • direction
  • page-transition-delay
  • page-transition-duration
  • reset-delay
  • show-speed-lines
  • smoke-density
  • smoke-lifetime

Properties

  • onTransitionComplete
  • verticalPosition

Slots

  • default

Events

  • detent-transition-complete — {}

Example

Runnable example: site/examples/car-smoke-page-transition.ts. The snippet below is generated from the catalogued attributes.

<detent-car-smoke-page-transition
  car-delay="0"
  car-duration="700"
  car-size="md"
  direction="left-to-right"
  page-transition-delay="550"
  page-transition-duration="350"
  reset-delay="300"
  show-speed-lines
  smoke-density="medium"
  smoke-lifetime="1200"
></detent-car-smoke-page-transition>

Property-only values

const element = document.querySelector('detent-car-smoke-page-transition');
if (element !== null) {
  // onTransitionComplete: () => void
  element.onTransitionComplete = () => {};
  // verticalPosition: 'top' | 'center' | 'bottom'
  element.verticalPosition = 'center';
}

Source

The pinned upstream source for this row:

  • src/components/ui/CarSmokePageTransition.tsx sha256 7c1a5d0f9863…

Native files

The native module and its artifacts:

  • src/components/car-smoke-page-transition/car-smoke-page-transition.ts
  • src/components/car-smoke-page-transition/styles.css
  • src/components/car-smoke-page-transition/define.ts
  • docs/components/car-smoke-page-transition.md
  • site/examples/car-smoke-page-transition.ts
  • tests/browser/car-smoke-page-transition.spec.ts
  • evidence/components/car-smoke-page-transition.md

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
car-delaycarDelaynumber0Kebab-case attribute; camelCase property.
car-durationcarDurationnumber700Kebab-case attribute; camelCase property.
car-sizecarSize'sm' | 'md' | 'lg''md'Kebab-case attribute; camelCase property.
directiondirection'left-to-right' | 'right-to-left''left-to-right'Kebab-case attribute; camelCase property.
page-transition-delaypageTransitionDelaynumber550Kebab-case attribute; camelCase property.
page-transition-durationpageTransitionDurationnumber350Kebab-case attribute; camelCase property.
reset-delayresetDelaynumber300Kebab-case attribute; camelCase property.
show-speed-linesshowSpeedLinesbooleantruePresence semantics: present means true, absent means false.
smoke-densitysmokeDensity'off' | 'low' | 'medium' | 'high''medium'Kebab-case attribute; camelCase property.
smoke-lifetimesmokeLifetimenumber1200Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
onTransitionComplete() => voidundefinedProperty-only: assign from script; not accepted as an attribute.
verticalPosition'top' | 'center' | 'bottom''center'Property-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-transition-completeonTransitionComplete() => voidComposed, bubbles; fires only for a user action.

Slots

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

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `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.
  • The overlay owns the pointer while it runs and is hidden/inert at idle; the upstream overlay is pointer-events-none.
  • The trigger keeps native semantics: upstream wrapped children in a role=button span with a hard-coded aria-label and its own key handler, while the native element makes the slotted control the trigger and calls preventDefault() without stopPropagation(), so consumer listeners still run (PC-11.6).
  • The fallback trigger is the pinned Launch Transition button.
  • Theme resolves through tokens and prefers-color-scheme instead of useTheme(); --detent-car-smoke-wipe is the override.
  • The dir=ltr wrapper is dropped because direction drives the geometry and the mirrored artwork.
  • showSpeedLines (default true) is reflected and the property is the opt-out.

What it does

A cinematic page transition: the slotted trigger launches a fixed overlay, a supercar travels across the viewport with body pitch/roll and spinning wheels, a canvas paints drift smoke, skid marks and speed lines, a wipe fades in, and detent-transition-complete fires when the choreography ends — the point at which the consumer navigates.

Upstream behaviour
  • Hardware-accelerated Canvas particle drift smoke physics and realistic skid marks
  • Choreographed multi-axis Ferrari drift movement, wheel spin, and speed lines
  • Customizable travel direction (left-to-right or right-to-left), density, and scale
  • Automatic theme awareness (light and dark mode overlay shading)
  • Full accessibility support with automatic prefers-reduced-motion fallback
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `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.
  • The overlay owns the pointer while it runs and is hidden/inert at idle; the upstream overlay is pointer-events-none.
  • The trigger keeps native semantics: upstream wrapped children in a role=button span with a hard-coded aria-label and its own key handler, while the native element makes the slotted control the trigger and calls preventDefault() without stopPropagation(), so consumer listeners still run (PC-11.6).
  • The fallback trigger is the pinned Launch Transition button.
  • Theme resolves through tokens and prefers-color-scheme instead of useTheme(); --detent-car-smoke-wipe is the override.
  • The dir=ltr wrapper is dropped because direction drives the geometry and the mirrored artwork.
  • showSpeedLines (default true) is reflected and the property is the opt-out.
Previous: Calendar Next: Chat

Docs

Components

Project