Skip to content

HySwitch

Usage examples on this page are written for Lit / plain HTML (<hy-switch>). The same component ships as HySwitch in @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 offon
  • 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 reason field ('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 offon
  • Tab: Moves focus to/from the switch
  • Enter: No effect (standard switch behavior - doesn't change state)

Examples

Basic toggle

html
<hy-switch name="notifications" value="enabled" label="Enable notifications"> </hy-switch>

Form submission

html
<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

html
<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

html
<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

html
<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

html
<hy-switch label="Disabled off" disabled></hy-switch>
<hy-switch label="Disabled on" state="on" disabled></hy-switch>

Programmatic control

javascript
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

css
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

html
<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

html
<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

javascript
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

PropertyAttributeTypeDefaultDescription
statestate'off' | 'on''off'The current state of the switch ('off' or 'on').
labellabelstring''The text label displayed next to the switch.
helpTexthelp-textstring''Helper text displayed below the switch for additional context.
topLabeltop-labelstring''Top label displayed above the switch, useful for grouping or section headers.
disableddisabledbooleanfalseDisables the switch, making it non-interactive.
requiredrequiredbooleanfalseMakes the switch required for form submission (must be in 'on' state).
namenamestring''The name of the switch for form submission.
valuevaluestring'on'The value submitted with the form when the switch is 'on'.
variantvariant'default' | 'error''default'The visual variant of the switch ('default' or 'error').
sizesize'small' | 'medium' | 'large''medium'The size of the switch ('small', 'medium', or 'large').
layoutlayout'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.
ariaDescribedbyaria-describedbystring''Additional element IDs to include in aria-describedby for accessibility.
switchIdswitch-idstring''The ID for the switch input element.
helperTextIdhelper-text-idstring''The ID for the helper text element.
topLabelIdtop-label-idstring''The ID for the top label element.
autofocusautofocusbooleanfalseMarks 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.
checkedcheckedbooleanBoolean convenience property that mirrors the state property. Returns true when state is 'on', false when 'off'. Setting this property will update the state accordingly.
formHTMLFormElement | nullReturns the form element that contains this switch, if any.
validityValidityStateReturns the ValidityState object for the switch.
validationMessagestringReturns the validation message for the switch.
rolestring'switch'

Events

EventDetailDescription
changeStandard form change event
inputStandard form input event

Slots

SlotDescription
top-labelContent above the switch
help-textContent below the switch
check-iconIcon rendered inside the thumb when state is on. Defaults to a check icon.
uncheck-iconIcon 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

PartDescription
baseThe switch's base wrapper element

CSS Custom Properties

PropertyDescription
--hy-switch-text-transformText transform for switch label
--hy-switch-thumb-shadowBox shadow for the switch thumb at rest
--hy-switch-thumb-shadow-hoverBox shadow for the switch thumb on hover
--hy-switch-font-familyFont family override for switch label
--hy-switch-focus-ring-widthFocus ring width override
--hy-switch-label-column-widthWidth of the label column in layout="inline" mode. No default — set on a wrapping container so stacked switches align.
--hy-switch-handle-smThumb 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-mdThumb size for the medium size variant. Default: var(--hy-indicator-size-md)
--hy-switch-handle-lgThumb size for the large size variant. Default: var(--hy-indicator-size-lg)
--hy-switch-height-smOuter 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-mdOuter container min-height for medium size variant. Default: var(--hy-control-height-md)
--hy-switch-height-lgOuter container min-height for large size variant. Default: var(--hy-control-height-lg)
--hy-switch-hover-durationTransition duration for hover and focus state feedback
--hy-switch-hover-easingTransition 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.

javascript
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.

javascript
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.

javascript
const switchEl = document.querySelector('hy-switch');
switchEl.toggle(); // off -> on or on -> off

turnOn()

Turns the switch to 'on' state programmatically. Does nothing if the switch is disabled.

javascript
const switchEl = document.querySelector('hy-switch');
switchEl.turnOn();

turnOff()

Turns the switch to 'off' state programmatically. Does nothing if the switch is disabled.

javascript
const switchEl = document.querySelector('hy-switch');
switchEl.turnOff();

focus()

Sets focus on the switch input.

javascript
const switchEl = document.querySelector('hy-switch');
switchEl.focus();

blur()

Removes focus from the switch input.

javascript
const switchEl = document.querySelector('hy-switch');
switchEl.blur();

Built with Lit. Documented with VitePress.