Detent web components
By Shane Logsdon

Buttons & actions

Drag to Confirm

Implemented

Spring-resistant slider for confirming destructive or critical operations

detent-drag-to-confirm

Port status
Catalog status
implemented
Origin
upstream
Phase
4
Wave
phase-4-motion-controls
Group
Buttons & actions

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

Variants

Local demo. Drag a handle past 85% of the track, or focus it and press Space, Enter or ArrowRight, to confirm. Releasing early snaps the handle back with the pinned spring. The slider above and the first two on this page auto-reset after 2.5 s and report detent-reset; the next is controlled by is-confirmed with the timer disabled; the last is disabled. Each confirmation reports detent-confirm with an empty detail and pulses the device where vibration is available — nothing is deleted, unlocked, submitted or archived.

No confirmation yet.

Recorded states

  • focus-visible recorded state; no setter
  • disabled recorded state; no setter
  • dragging recorded state; no setter
  • dark theme recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/drag-to-confirm/define.js';

// or register it yourself
import { DetentDragToConfirm } from '/components/drag-to-confirm/drag-to-confirm.js';

Attributes

  • action-type
  • auto-reset-delay
  • confirmed-label
  • disabled
  • haptic
  • is-confirmed
  • label
  • size
  • variant

Properties

  • onConfirm
  • onReset

Slots

  • confirmed-icon
  • icon

Events

  • detent-confirm — {}
  • detent-reset — {}

Example

Runnable example: site/examples/drag-to-confirm.ts. The snippet below is generated from the catalogued attributes.

<detent-drag-to-confirm
  action-type="confirm"
  auto-reset-delay="2500"
  confirmed-label="Confirmed ✓"
  disabled
  haptic
  is-confirmed
  label="Slide to confirm"
  size="md"
  variant
></detent-drag-to-confirm>

Property-only values

const element = document.querySelector('detent-drag-to-confirm');
if (element !== null) {
  // onConfirm: () => void
  element.onConfirm = () => {};
  // onReset: () => void
  element.onReset = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/DragToConfirm.tsx sha256 55f59fe6a48d…

Native files

The native module and its artifacts:

  • src/components/drag-to-confirm/drag-to-confirm.ts
  • src/components/drag-to-confirm/styles.css
  • src/components/drag-to-confirm/define.ts
  • docs/components/drag-to-confirm.md
  • site/examples/drag-to-confirm.ts
  • tests/browser/drag-to-confirm.spec.ts
  • evidence/components/drag-to-confirm.md

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
action-typeactionType'delete' | 'archive' | 'confirm' | 'submit' | 'unlock' | 'continue''confirm'Kebab-case attribute; camelCase property.
auto-reset-delayautoResetDelaynumber2500Kebab-case attribute; camelCase property.
confirmed-labelconfirmedLabelstring'Confirmed ✓'Kebab-case attribute; camelCase property.
disableddisabledbooleanfalsePresence semantics: present means true, absent means false.
haptichapticbooleantruePresence semantics: present means true, absent means false.
is-confirmedisConfirmedbooleanundefinedPresence semantics: present means true, absent means false.
labellabelstring'Slide to confirm'Kebab-case attribute; camelCase property.
sizesize'sm' | 'md' | 'lg''md'Kebab-case attribute; camelCase property.
variantvariant'default' | 'danger' | 'warning' | 'info' | 'success'undefinedKebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
onConfirm() => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onReset() => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

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

Slots

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

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • 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.
  • The spring retarget is position-continuous but velocity restarts at zero because the shared helper starts at rest; springSnappy 380/30/0.5 for snapback and springResponsive 320/24/0.7 for reset and controlled return are kept.
  • The fallback action button becomes visible while focused (upstream clipped it permanently), an accessibility correction.
  • Boundary, fill and tone colours derive from theme tokens so both schemes reach the WCAG 1.4.11 non-text ratio.
  • disabled also cancels an in-flight gesture and returns the handle to rest.
  • A controlled is-confirmed slider emits detent-confirm but does not move, and the auto-reset timer still emits detent-reset.
  • The disabled state keeps its label at full text contrast and carries the inactive look in the chrome instead: the pill boundary falls back to the border token, the handle loses its shadow, its glyph is mixed toward the handle surface and the handle alone is at 60% opacity. Fading the whole pill to 40% had painted the label at 1.92:1 light and 2.56:1 dark.

What it does

A pill-shaped slider you drag to the right to confirm a destructive or critical action, with a progressive fill, a spring snapback on incomplete drags, an optional auto-reset timer and an optional haptic pulse.

Upstream behaviour
  • Clean minimalist pill geometry with seamless light and dark mode theme compatibility
  • Dynamic progressive track fill that physically tracks the handle as you drag
  • Physical gesture drag handle with calibrated spring snapback upon incomplete drags
  • Supports Delete, Archive, Confirm, Submit, and Unlock action profiles with custom sizes (sm, md, lg)
  • Automatic post-confirmation reset timer with customizable delay or controlled state support
  • Full keyboard accessibility (Space, Enter, ArrowRight to trigger) and mobile haptic vibration feedback
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • 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.
  • The spring retarget is position-continuous but velocity restarts at zero because the shared helper starts at rest; springSnappy 380/30/0.5 for snapback and springResponsive 320/24/0.7 for reset and controlled return are kept.
  • The fallback action button becomes visible while focused (upstream clipped it permanently), an accessibility correction.
  • Boundary, fill and tone colours derive from theme tokens so both schemes reach the WCAG 1.4.11 non-text ratio.
  • disabled also cancels an in-flight gesture and returns the handle to rest.
  • A controlled is-confirmed slider emits detent-confirm but does not move, and the auto-reset timer still emits detent-reset.
  • The disabled state keeps its label at full text contrast and carries the inactive look in the chrome instead: the pill boundary falls back to the border token, the handle loses its shadow, its glyph is mixed toward the handle surface and the handle alone is at 60% opacity. Fading the whole pill to 40% had painted the label at 1.92:1 light and 2.56:1 dark.
Previous: Dot Shader Next: Checkbox

Docs

Components

Project