Detent web components
By Shane Logsdon

Media & effects

Torque Dial

Implemented

Rotational control knob with physical spin momentum

detent-torque-dial

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

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

Variants

Drag a dial, use the wheel, or focus and use the arrow keys. Nothing has changed yet.

Recorded states

  • dragging recorded state; no setter

Usage

Import

import '/components/torque-dial/define.js';

// or register it yourself
import { DetentTorqueDial } from '/components/torque-dial/torque-dial.js';

Attributes

  • disabled
  • label
  • max
  • min
  • momentum
  • show-value
  • size
  • step
  • unit
  • value

Properties

  • onChange
  • onChangeEnd

Slots

No slots are recorded for this component.

Events

  • detent-change — {}
  • detent-change-end — {}

Example

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

<detent-torque-dial
  disabled
  label
  max="100"
  min="0"
  momentum
  show-value
  size="120"
  step="1"
  unit=""
  value
></detent-torque-dial>

Property-only values

const element = document.querySelector('detent-torque-dial');
if (element !== null) {
  // onChange: (val: number) => void
  element.onChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/TorqueDial.tsx sha256 9edabeddc493…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
disabled———Recorded in the catalog as an attribute.
label———Recorded in the catalog as an attribute.
maxmaxnumber100Kebab-case attribute; camelCase property.
minminnumber0Kebab-case attribute; camelCase property.
momentummomentumbooleantruePresence semantics: present means true, absent means false.
show-value———Recorded in the catalog as an attribute.
sizesizenumber120Kebab-case attribute; camelCase property.
stepstepnumber1Kebab-case attribute; camelCase property.
unitunitstring''Kebab-case attribute; camelCase property.
valuevaluenumber—Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
onChange(val: number) => void—Property-only: assign from script; not accepted as an attribute.
onChangeEnd——Recorded in the catalog without a top-level type; pinned from source when the row is ported.

Events

Events
EventUpstream propPayload typeNotes
detent-changeonChange(val: number) => voidComposed, bubbles; fires only for a user action.
detent-change-endonChangeEnd() => 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.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
  • Knob rotation and progress arc share one springSnappy (380/30/0.5) spring on the shared helper, started from the painted angle; a retarget continues from there. Upstream ran two springs with the same token, so the arc and knob trajectories match; no easing is substituted.
  • The progress arc follows the value instead of springing.
  • An absent value resolves to min.
  • min/max/step changes re-clamp the stored value.
  • A no-op step emits no detent-change and a no-op gesture emits no detent-change-end.
  • Track, knob border and muted label are lightened for contrast (upstream measured 1.21:1, 1.12:1 and 3.74:1).
  • The release glide decay is normalised by elapsed time instead of per-frame multiply 0.92.
  • className is not a property.

What it does

A rotational knob with pointer-drag value mapping, wheel and keyboard stepping, double-click centring, an angular release glide and an SVG radial progress ring.

Upstream behaviour
  • Natural angular momentum release with exponential friction deceleration decay
  • Supports drag rotation, mouse wheel fine-tuning, keyboard arrow adjustments, and double-click centering
  • Dynamic SVG radial progress ring with continuous value telemetry
  • Full accessibility with role="slider", aria-valuenow, and keyboard stepping
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.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
  • Knob rotation and progress arc share one springSnappy (380/30/0.5) spring on the shared helper, started from the painted angle; a retarget continues from there. Upstream ran two springs with the same token, so the arc and knob trajectories match; no easing is substituted.
  • The progress arc follows the value instead of springing.
  • An absent value resolves to min.
  • min/max/step changes re-clamp the stored value.
  • A no-op step emits no detent-change and a no-op gesture emits no detent-change-end.
  • Track, knob border and muted label are lightened for contrast (upstream measured 1.21:1, 1.12:1 and 3.74:1).
  • The release glide decay is normalised by elapsed time instead of per-frame multiply 0.92.
  • className is not a property.
Previous: Thinking Orb Next: Typewriter Button

Docs

Components

Project