HySwitch
Usage examples on this page are written for Lit / plain HTML (
<hy-switch>). The same component ships asHySwitchin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A form-associated switch component that toggles between on and off states. Provides comprehensive form integration via the ElementInternals API, accessibility features, and customizable styling.
Key Behavioral Model
This switch implements a clear separation between user interactions and programmatic control:
User Interactions (Mouse clicks, Space key)
- Binary states: Toggle between
off↔on - Standard behavior: Follows native switch/toggle conventions
- Predictable UX: Users get consistent toggle behavior
Programmatic Control (JavaScript methods)
- Method-controlled: Use
setState(),toggle(),turnOn(),turnOff() - Event tracking: All changes include
reasonfield ('user' | 'programmatic')
Form Integration
This component implements the Form-Associated Custom Elements API, allowing it to participate in form submission just like native checkboxes. The switch's value will be included in FormData when in the "on" state and the form is submitted.
Usage Guidelines
Switches are ideal for settings that take effect immediately and represent binary on/off states. Use switches for enabling/disabling features, toggling preferences, or controlling visibility. For selections that require a form submission, consider using checkboxes instead.
Sizes
- small: Compact switch for dense layouts or inline controls
- medium (default): Standard switch size for most use cases
- large: Prominent switch for emphasis or improved touch targets
Variants
- default: Standard switch appearance
- error: Red-themed switch indicating error state or required field validation
States
- off: Inactive state (default)
- on: Active/enabled state
- disabled: Non-interactive state (can be combined with off/on)
Keyboard Navigation
- Space key: Toggles between
off↔on - Tab: Moves focus to/from the switch
- Enter: No effect (standard switch behavior - doesn't change state)
Examples
Basic toggle
<hy-switch name="notifications" value="enabled" label="Enable notifications"> </hy-switch>Form submission
<form>
<hy-switch name="dark-mode" value="enabled" label="Dark mode"> </hy-switch>
<button type="submit">Save</button>
</form>With top label and help text
<hy-switch
name="newsletter"
value="subscribed"
top-label="Newsletter Preferences"
label="Subscribe to weekly newsletter"
help-text="Get the latest updates delivered to your inbox"
>
</hy-switch>Required field with error variant
<form>
<hy-switch
name="terms"
value="accepted"
label="Accept terms and conditions"
help-text="You must accept to continue"
required
variant="error"
>
</hy-switch>
<button type="submit">Submit</button>
</form>Different sizes
<hy-switch size="small" label="Small switch"></hy-switch>
<hy-switch size="medium" label="Medium switch"></hy-switch>
<hy-switch size="large" label="Large switch"></hy-switch>Disabled states
<hy-switch label="Disabled off" disabled></hy-switch>
<hy-switch label="Disabled on" state="on" disabled></hy-switch>Programmatic control
const switchElement = document.querySelector('hy-switch');
// Turn on
switchElement.turnOn();
// Turn off
switchElement.turnOff();
// Toggle
switchElement.toggle();
// Check state
console.log(switchElement.checked); // true/false
console.log(switchElement.state); // 'on'/'off'
// Form validation
if (switchElement.checkValidity()) {
console.log('Valid');
} else {
switchElement.reportValidity();
}Event handling
switchElement.addEventListener('change', (e) => {
console.log({
state: e.detail.state, // 'on' or 'off'
checked: e.detail.checked, // true or false
value: e.detail.value, // switch's value attribute
reason: e.detail.reason // 'user' or 'programmatic'
});
// Save setting immediately
if (e.detail.reason === 'user') {
savePreference(e.detail.checked);
}
});Using slots for dynamic content
<hy-switch name="privacy" value="enabled">
<span slot="top-label">
Privacy Settings
<hy-icon name="shield"></hy-icon>
</span>
<span slot="help-text"> Learn more about our <a href="/privacy">privacy policy</a> </span>
</hy-switch>Complete settings interface
<form>
<fieldset>
<legend>Notification Preferences</legend>
<hy-switch
name="email-notifications"
value="enabled"
label="Email notifications"
help-text="Receive updates via email"
state="on"
>
</hy-switch>
<hy-switch
name="push-notifications"
value="enabled"
label="Push notifications"
help-text="Receive push notifications in browser"
state="on"
>
</hy-switch>
<hy-switch
name="sms-notifications"
value="enabled"
label="SMS notifications"
help-text="Receive text message alerts"
disabled
>
</hy-switch>
</fieldset>
<button type="submit">Save Preferences</button>
</form>Programmatic Methods
const switchEl = document.querySelector('hy-switch');
// State control
switchEl.turnOn();
switchEl.turnOff();
switchEl.toggle();
// Form validation
switchEl.checkValidity();
switchEl.reportValidity();
// Focus management
switchEl.focus();
switchEl.blur();
// Access form
console.log(switchEl.form);
console.log(switchEl.validity);
console.log(switchEl.validationMessage);API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
state | state | 'off' | 'on' | 'off' | The current state of the switch ('off' or 'on'). |
label | label | string | '' | The text label displayed next to the switch. |
helpText | help-text | string | '' | Helper text displayed below the switch for additional context. |
topLabel | top-label | string | '' | Top label displayed above the switch, useful for grouping or section headers. |
disabled | disabled | boolean | false | Disables the switch, making it non-interactive. |
required | required | boolean | false | Makes the switch required for form submission (must be in 'on' state). |
name | name | string | '' | The name of the switch for form submission. |
value | value | string | 'on' | The value submitted with the form when the switch is 'on'. |
variant | variant | 'default' | 'error' | 'default' | The visual variant of the switch ('default' or 'error'). |
size | size | 'small' | 'medium' | 'large' | 'medium' | The size of the switch ('small', 'medium', or 'large'). |
layout | layout | 'block' | 'inline' | 'block' | Row layout: block (default) stacks topLabel above the switch; inline puts topLabel on the left of the row and the switch on the right. Help text always sits below the row — see the Inline Layout Convention in .claude/rules/components.md. Only meaningful when topLabel is set. Set --hy-switch-label-column-width on a wrapping container so stacked switches align. |
ariaDescribedby | aria-describedby | string | '' | Additional element IDs to include in aria-describedby for accessibility. |
switchId | switch-id | string | '' | The ID for the switch input element. |
helperTextId | helper-text-id | string | '' | The ID for the helper text element. |
topLabelId | top-label-id | string | '' | The ID for the top label element. |
autofocus | autofocus | boolean | false | Marks this switch as the auto-focus target. Read by hy-dialog (and other overlay containers) on show to choose initial focus. Also focuses on first connect when used outside an overlay, mirroring native HTML autofocus but routed through JS so SSR'd HTML doesn't pull focus during hydration. |
checked | checked | boolean | — | Boolean convenience property that mirrors the state property. Returns true when state is 'on', false when 'off'. Setting this property will update the state accordingly. |
form | — | HTMLFormElement | null | — | Returns the form element that contains this switch, if any. |
validity | — | ValidityState | — | Returns the ValidityState object for the switch. |
validationMessage | — | string | — | Returns the validation message for the switch. |
role | — | string | 'switch' | — |
Events
| Event | Detail | Description |
|---|---|---|
change | — | Standard form change event |
input | — | Standard form input event |
Slots
| Slot | Description |
|---|---|
top-label | Content above the switch |
help-text | Content below the switch |
check-icon | Icon rendered inside the thumb when state is on. Defaults to a check icon. |
uncheck-icon | Icon rendered inside the thumb when state is off. Empty by default; useful for binary-meaning switches (e.g. sun/moon for a theme toggle). |
CSS Parts
| Part | Description |
|---|---|
base | The switch's base wrapper element |
CSS Custom Properties
| Property | Description |
|---|---|
--hy-switch-text-transform | Text transform for switch label |
--hy-switch-thumb-shadow | Box shadow for the switch thumb at rest |
--hy-switch-thumb-shadow-hover | Box shadow for the switch thumb on hover |
--hy-switch-font-family | Font family override for switch label |
--hy-switch-focus-ring-width | Focus ring width override |
--hy-switch-label-column-width | Width of the label column in layout="inline" mode. No default — set on a wrapping container so stacked switches align. |
--hy-switch-handle-sm | Thumb size for the small size variant; the track derives from the thumb, so this scales the whole control. Default: var(--hy-indicator-size-sm) |
--hy-switch-handle-md | Thumb size for the medium size variant. Default: var(--hy-indicator-size-md) |
--hy-switch-handle-lg | Thumb size for the large size variant. Default: var(--hy-indicator-size-lg) |
--hy-switch-height-sm | Outer container min-height for small size variant. Per-component override in the form-control height contract (see .claude/rules/control-heights.md). Track and handle ride the size's --hy-switch-handle-* rung inside; the host floors to form-row baseline so switches align with selects/inputs in inline rows. Default: var(--hy-control-height-sm) |
--hy-switch-height-md | Outer container min-height for medium size variant. Default: var(--hy-control-height-md) |
--hy-switch-height-lg | Outer container min-height for large size variant. Default: var(--hy-control-height-lg) |
--hy-switch-hover-duration | Transition duration for hover and focus state feedback |
--hy-switch-hover-easing | Transition easing for hover and focus state feedback |
Methods
checkValidity()
Checks if the switch is valid according to form validation rules. Returns true if valid, false otherwise.
const switchEl = document.querySelector('hy-switch');
if (!switchEl.checkValidity()) {
console.log('Switch is invalid');
}reportValidity()
Checks validity and shows browser validation UI if invalid. Returns true if valid, false otherwise.
const switchEl = document.querySelector('hy-switch');
if (!switchEl.reportValidity()) {
console.log('Switch is invalid and message shown');
}toggle()
Toggles the switch between 'on' and 'off' states programmatically. Does nothing if the switch is disabled.
const switchEl = document.querySelector('hy-switch');
switchEl.toggle(); // off -> on or on -> offturnOn()
Turns the switch to 'on' state programmatically. Does nothing if the switch is disabled.
const switchEl = document.querySelector('hy-switch');
switchEl.turnOn();turnOff()
Turns the switch to 'off' state programmatically. Does nothing if the switch is disabled.
const switchEl = document.querySelector('hy-switch');
switchEl.turnOff();focus()
Sets focus on the switch input.
const switchEl = document.querySelector('hy-switch');
switchEl.focus();blur()
Removes focus from the switch input.
const switchEl = document.querySelector('hy-switch');
switchEl.blur();