Detent web components
By Shane Logsdon

Buttons & actions

Blob Toggle

Implemented

Morphing liquid blob toggle

detent-blob-toggle

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

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

Variants

Recorded states

  • disabled recorded state; no setter
  • checked recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/blob-toggle/define.js';

// or register it yourself
import { DetentBlobToggle } from '/components/blob-toggle/blob-toggle.js';

Attributes

  • accent-color
  • default-value
  • disabled
  • height
  • off-label
  • on-label
  • show-labels
  • value
  • width

Properties

  • value
  • defaultValue
  • disabled
  • offLabel
  • onLabel
  • accentColor
  • showLabels
  • width
  • height
  • onChange

Slots

No slots are recorded for this component.

Events

  • detent-change — { value: boolean }

Example

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

<detent-blob-toggle
  accent-color="#FAFAFA"
  default-value
  disabled
  height="32"
  off-label="Off"
  on-label="On"
  show-labels
  value
  width="56"
></detent-blob-toggle>

Property-only values

const element = document.querySelector('detent-blob-toggle');
if (element !== null) {
  // onChange: (value: boolean) => void
  element.onChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/LiquidToggle.tsx sha256 e42922a6423d…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
accent-coloraccentColorstring'#FAFAFA'Kebab-case attribute; camelCase property.
default-valuedefaultValuebooleanfalsePresence semantics: present means true, absent means false.
disableddisabledbooleanfalsePresence semantics: present means true, absent means false.
heightheightnumber32Kebab-case attribute; camelCase property.
off-labeloffLabelstring'Off'Kebab-case attribute; camelCase property.
on-labelonLabelstring'On'Kebab-case attribute; camelCase property.
show-labelsshowLabelsbooleantruePresence semantics: present means true, absent means false.
valuevaluebooleanundefinedPresence semantics: present means true, absent means false.
widthwidthnumber56Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
valuebooleanundefinedProperty-only: assign from script; not accepted as an attribute.
defaultValuebooleanfalseProperty-only: assign from script; not accepted as an attribute.
disabledbooleanfalseProperty-only: assign from script; not accepted as an attribute.
offLabelstring'Off'Property-only: assign from script; not accepted as an attribute.
onLabelstring'On'Property-only: assign from script; not accepted as an attribute.
accentColorstring'#FAFAFA'Property-only: assign from script; not accepted as an attribute.
showLabelsbooleantrueProperty-only: assign from script; not accepted as an attribute.
widthnumber56Property-only: assign from script; not accepted as an attribute.
heightnumber32Property-only: assign from script; not accepted as an attribute.
onChange(value: boolean) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-changeonChange(value: boolean) => 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.
  • 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.
  • `value` plus `default-value` collapse the React controlled/uncontrolled pair, and programmatic writes emit nothing.
  • `onChange` becomes `detent-change`.
  • accentColor's theme-aware default becomes a token mix while the catalog records '#FAFAFA'.
  • `show-labels` resolves true, is reflected, and the property is the opt-out.
  • The travel spring keeps 280/24/0.55 through the shared helper, while the upstream second stretch spring (240/18/0.4) becomes a derived per-frame stretch (PC-10.4).
  • Visible text is the off/on labels uppercased instead of a hardcoded OFF/ON (WCAG 2.5.3).
  • The track boundary is a color-mix because upstream border-border measures 1.4:1 on white; disabled mutes track and blob but never fades the text (upstream opacity-40 measured 1.9:1).
  • Width and height clamp with diagnostics (PC-3.4); the element is not form-associated, so there is no fieldset inheritance.

What it does

A switch whose blob is one SVG path that morphs between the off and on positions. The path is rebuilt every frame from the spring-smoothed progress and a transient stretch along the travel axis, so the blob deforms as it travels instead of sliding.

Upstream behaviour
  • Internal blob shape continuously morphs between states
  • Transient stretch pulse along the travel axis when toggling
  • Spring-smoothed progress with mass/damping tuning
  • Controlled and uncontrolled usage via value/defaultValue/onChange
  • Respects prefers-reduced-motion (uses static state fallback)
Differences from upstream
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • 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.
  • `value` plus `default-value` collapse the React controlled/uncontrolled pair, and programmatic writes emit nothing.
  • `onChange` becomes `detent-change`.
  • accentColor's theme-aware default becomes a token mix while the catalog records '#FAFAFA'.
  • `show-labels` resolves true, is reflected, and the property is the opt-out.
  • The travel spring keeps 280/24/0.55 through the shared helper, while the upstream second stretch spring (240/18/0.4) becomes a derived per-frame stretch (PC-10.4).
  • Visible text is the off/on labels uppercased instead of a hardcoded OFF/ON (WCAG 2.5.3).
  • The track boundary is a color-mix because upstream border-border measures 1.4:1 on white; disabled mutes track and blob but never fades the text (upstream opacity-40 measured 1.9:1).
  • Width and height clamp with diagnostics (PC-3.4); the element is not form-associated, so there is no fieldset inheritance.
Previous: Ripple Button Next: Live Line Chart

Docs

Components

Project