cv-switch
Toggle control that represents an on/off state, visually distinct from a checkbox.
Headless: createSwitch
Usage
Anatomy
<cv-switch> (host)
└── <div part="base">
├── <div part="control" role="switch">
│ ├── <span part="toggled" hidden>
│ │ └── <slot name="toggled">
│ ├── <span part="untoggled" hidden>
│ │ └── <slot name="untoggled">
│ └── <span part="thumb">
│ └── <span part="loader" aria-hidden="true"> (when loading)
├── <span part="label">
│ └── <slot>
└── <span part="help-text" id="{idBase}-help-text">
└── <slot name="help-text">When checked is true, the toggled wrapper is visible and untoggled is hidden. When checked is false, the opposite applies. Both wrappers are always in the DOM; visibility is toggled via CSS or the hidden attribute.
The help-text part is rendered only when the help-text attribute is set or the help-text slot is populated.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
checked | Boolean | false | On/off state |
disabled | Boolean | false | Prevents interaction and disables the form-associated value |
loading | Boolean | false | Shows pending state and blocks interaction without disabling value |
size | String | "medium" | Size: small | medium | large |
help-text | String | "" | Descriptive text displayed below the switch |
Sizes
| Size | --cv-switch-width | --cv-switch-height | --cv-switch-thumb-size |
|---|---|---|---|
small | 36px | 20px | 14px |
medium | 44px | 24px | 18px |
large | 52px | 28px | 22px |
Slots
| Slot | Description |
|---|---|
(default) | Label text displayed beside the switch |
toggled | Content shown inside the track when checked (e.g., icon, text) |
untoggled | Content shown inside the track when unchecked (e.g., icon, text) |
help-text | Descriptive text below the switch; overrides the help-text attribute |
CSS Parts
| Part | Element | Description |
|---|---|---|
base | <div> | Root layout wrapper (contains control + label + help-text) |
control | <div> | Track/oval with role="switch" |
thumb | <span> | Sliding knob inside the control |
loader | <span> | CSS-only pending spinner rendered inside thumb when loading |
toggled | <span> | Wrapper around the toggled slot (visible when checked) |
untoggled | <span> | Wrapper around the untoggled slot (visible when unchecked) |
label | <span> | Wrapper around the default slot |
help-text | <span> | Wrapper around the help-text slot or attribute text |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--cv-switch-width | 44px | Minimum inline size of the control track |
--cv-switch-height | 24px | Block size of the control track |
--cv-switch-thumb-size | 18px | Size of the thumb knob |
--cv-switch-gap | var(--cv-space-2, 8px) | Spacing between control track and label |
--cv-switch-track-content-gap | 4px | Spacing between thumb and toggled/untoggled slot |
--cv-switch-loader-size | calc(var(--cv-switch-thumb-size) * 0.58) | Size of the loading spinner inside the thumb |
--cv-switch-loader-stroke | 2px | Stroke width of the loading spinner |
--cv-switch-help-text-color | var(--cv-color-text-muted, #9aa6bf) | Color of the help text |
--cv-switch-help-text-font-size | 0.85em | Font size of the help text |
Visual States
| Host selector | Description |
|---|---|
:host([checked]) | Primary-tinted track, thumb translated to end position; toggled part visible, untoggled part hidden |
:host([loading]) | Progress cursor and loader inside the thumb; current checked visual state remains visible |
:host([disabled]) | Reduced opacity (0.55), cursor: not-allowed |
:host([size="small"]) | Small size overrides |
:host([size="large"]) | Large size overrides |
Reactive State Mapping
cv-switch is a visual adapter over headless createSwitch.
| UIKit Property | Direction | Headless Binding |
|---|---|---|
checked | attr -> action | actions.setOn(value) |
disabled | attr -> action | actions.setDisabled(value) |
loading | attr -> action | actions.setLoading(value) |
help-text | attr -> option | When present, generates an id for the help-text element and passes it as ariaDescribedBy in createSwitch(options) |
| Headless State | Direction | DOM Reflection |
|---|---|---|
state.isOn() | state -> attr | [checked] host attribute |
state.isDisabled() | state -> attr | [disabled] host attribute |
state.isLoading() | state -> attr | aria-busy, aria-disabled, and tabindex on [part="control"] |
contracts.getSwitchProps()is spread onto the inner[part="control"]element to applyrole,aria-checked,aria-disabled,aria-busy,tabindex, and keyboard/click handlers.- When help text is present (via attribute or slot), the component generates the id
{idBase}-help-textfor the help-text element and passes it as theariaDescribedByoption tocreateSwitch, which produces thearia-describedbyattribute ingetSwitchProps(). - UIKit dispatches
cv-inputandcv-changeevents by observingisOnchanges triggered by user activation (not by controlledsetOn). loadingblocks click, host.click(),Enter, andSpaceactivation, but controlledcheckedupdates, form reset, form restore, and form-associated value submission remain available.- Toggled/untoggled slot visibility is purely visual (CSS-driven); no headless state or ARIA changes are involved.
- Toggled/untoggled slot content participates in track sizing;
--cv-switch-widthis a minimum, not a fixed width. - UIKit does not own toggle or keyboard logic; headless state is the source of truth.
Events
| Event | Detail | Description |
|---|---|---|
cv-input | {checked: boolean} | Fires on toggle interaction when not disabled/loading |
cv-change | {checked: boolean} | Fires when checked state commits from user activation |