Buttons & actions
Blob Toggle
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
disabledrecorded state; no settercheckedrecorded state; no setterreduced motionrecorded 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-colordefault-valuedisabledheightoff-labelon-labelshow-labelsvaluewidth
Properties
valuedefaultValuedisabledoffLabelonLabelaccentColorshowLabelswidthheightonChange
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.tsxsha256 e42922a6423d…
Native files
The native module and its artifacts:
src/components/blob-toggle/blob-toggle.tssrc/components/blob-toggle/styles.csssrc/components/blob-toggle/define.tsdocs/components/blob-toggle.mdsite/examples/blob-toggle.tstests/browser/blob-toggle.spec.tsevidence/components/blob-toggle.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
accent-color | accentColor | string | '#FAFAFA' | Kebab-case attribute; camelCase property. |
default-value | defaultValue | boolean | false | Presence semantics: present means true, absent means false. |
disabled | disabled | boolean | false | Presence semantics: present means true, absent means false. |
height | height | number | 32 | Kebab-case attribute; camelCase property. |
off-label | offLabel | string | 'Off' | Kebab-case attribute; camelCase property. |
on-label | onLabel | string | 'On' | Kebab-case attribute; camelCase property. |
show-labels | showLabels | boolean | true | Presence semantics: present means true, absent means false. |
value | value | boolean | undefined | Presence semantics: present means true, absent means false. |
width | width | number | 56 | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
value | boolean | undefined | Property-only: assign from script; not accepted as an attribute. |
defaultValue | boolean | false | Property-only: assign from script; not accepted as an attribute. |
disabled | boolean | false | Property-only: assign from script; not accepted as an attribute. |
offLabel | string | 'Off' | Property-only: assign from script; not accepted as an attribute. |
onLabel | string | 'On' | Property-only: assign from script; not accepted as an attribute. |
accentColor | string | '#FAFAFA' | Property-only: assign from script; not accepted as an attribute. |
showLabels | boolean | true | Property-only: assign from script; not accepted as an attribute. |
width | number | 56 | Property-only: assign from script; not accepted as an attribute. |
height | number | 32 | Property-only: assign from script; not accepted as an attribute. |
onChange | (value: boolean) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-change | onChange | (value: boolean) => void | Composed, 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.