Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Switch

Switch represents a single setting that turns on or off the moment someone toggles it, with no separate save step. It shares its form participation, validation, and keyboard behavior with checkbox, since a switch is really a checkbox with a different look and a different sense of urgency, not a different mechanism underneath. It's not a wrapper around a native input either: the whole element behaves as the control itself, so its accessible name, checked state, and validation all come from its own properties.

Dependencies

eun-icon · unless hide-icon is set or a custom icon is slotted eun-label · when using the label property instead of slotted content
Overview API Examples Accessibility
Enable notifications

When to use

Use a switch for a setting that takes effect the moment it's toggled, with no "Save" button needed, such as enabling notifications or switching to dark mode. See Alternatives below for a better fit when the choice only applies once the whole form is submitted, or when it's really about picking one option out of several.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/switch.js";
<eun-switch name="notifications" value="enabled">
  Enable notifications
</eun-switch>

Importing the file registers <eun-switch> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers A choice that only applies once the whole form is submitted eun-checkbox Picking one option out of several eun-radio

Guidance

  • Use a switch for a setting that takes effect immediately when toggled (e.g. "Enable notifications", "Dark mode")
  • Label it with the setting it controls, not its current state: "Notifications", not "Notifications are on" (see the Accessibility tab)
  • Pair a required switch with a visible note at the top of the form, not just the error message alone
  • Using a switch for a choice that only applies once the form is submitted, or to pick one option among several: see Alternatives above
  • Hiding the hint in a tooltip instead of rendering it under the field
  • Overriding colors with inline styles instead of the --switch-* CSS variables

Live testing

Properties

Switch <eun-switch>

Attributes

NameTypeDefaultDescription
default-checkedbooleanThe checked state applied when the switch connects, and restored on form reset
hide-iconbooleanfalseWhether the icon inside the thumb is hidden
hide-errorbooleanfalseWhether the error message is hidden
default-valuestringHas no effect on the switch. A form reset restores the checked state from defaultChecked instead
labelstringThe label to display when not using the default slot

Import the exact TypeScript type behind any property above, see Types.

Properties

JS-only — no matching HTML attribute, set these from a script or a template binding.

NameTypeDefaultDescription
checkedbooleanfalseWhether the switch is on
defaultCheckedbooleanThe checked state applied when the switch connects, and restored on form reset
disabledbooleanfalseWhether the switch is disabled
readonlybooleanfalseWhether the switch is read only
hideErrorbooleanfalseWhether the error message is hidden
requiredbooleanfalseWhether the switch must be turned on to be valid
namestringThe name of the switch field
valuestringThe value submitted with the switch
hintstringA short message displayed under the switch
validatorsArray<Validators<boolean>>A list of validation rules applied to the checked state

Slots

NameDescription
(default)The switch label
icon-onReplaces the default icon shown in the thumb while checked. Ignored when hideIcon is set
icon-offReplaces the default icon shown in the thumb while unchecked. Ignored when hideIcon is set

Events

NameTypeDescription
clickFired when the switch is clicked
eunchangeChangeEventFired when the switch's checked state changes

Every event above follows the same naming convention, covered in Events.

CSS custom properties

NameDescription
--switch-track-widthSets the width of the track
--switch-track-heightSets the height of the track
--switch-track-backgroundSets the track color while off
--switch-track-background-checkedSets the track color while on
--switch-track-background-hoverSets the track color on hover while off
--switch-track-background-pressedSets the track color while pressed and off
--switch-thumb-sizeSets the width and height of the thumb, independently of the track height
--switch-thumb-backgroundSets the color of the thumb
--switch-thumb-background-checkedSets the color of the thumb while checked, falling back to switch-thumb-background when unset
--switch-icon-colorSets the color of the icon inside the thumb
--switch-icon-color-checkedSets the color of the icon while checked, falling back to switch-icon-color when unset
--switch-error-colorSets the color of the error message
--switch-hint-colorSets the color of the hint message
--switch-focus-outline-colorSets the color of the focus outline

Basic

Enable notifications
<eun-switch name="notifications" value="enabled">
  Enable notifications
</eun-switch>

States

Checked

Set checked to render the switch already on.

Enable notifications
<eun-switch name="notifications" value="enabled" checked>
  Enable notifications
</eun-switch>

Disabled

Disabled switches remain visible but can't be toggled, and are excluded from the tab order and from form submission. A disabled switch always renders as unchecked, regardless of its checked property.

Disabled
<eun-switch disabled>Disabled</eun-switch>

Readonly

A readonly switch looks active, stays focusable, and its value still submits, but it can't be toggled by the user. Unlike disabled, it still communicates its current state instead of looking inert.

Readonly, off Readonly, on
<eun-switch readonly>Readonly, off</eun-switch>
<eun-switch readonly checked>Readonly, on</eun-switch>

Required

A required switch must be toggled on to be considered valid.

Accept marketing emails
<eun-switch required>Accept marketing emails</eun-switch>

With hint

hint renders a short message under the switch, replaced by the error message once the switch becomes invalid (see Error below).

Enable notifications
<eun-switch hint="You can change this anytime in settings">
  Enable notifications
</eun-switch>

Error

If a hint is defined, it shares the same space as the error message, and the error message always takes priority.

Enable notifications
<eun-switch required hint="Enable this setting to continue">
  Enable notifications
</eun-switch>
document.querySelector("eun-switch").validators = [
  {
    isValid: (checked: boolean) => checked,
    message: "Enable this setting to continue",
  },
];

Forms

Like every other Eunomia field, eun-switch is a real form-associated custom element that participates in a native <form>'s submission and reset. Two things about reset specifically catch people out, since a switch's reset semantics follow checked, not value, unlike a plain text field:

  • default-checked is what a reset restores, not default-value. default-value exists on eun-switch (inherited from the same base as every other field) but has no effect here: value is a fixed submission string (value="enabled"), not something the user edits, so there's nothing for default-value to meaningfully "restore" it to. Only default-checked (or plain checked, on connection) decides what a reset reverts to. Set it explicitly if the switch should come back on, since it otherwise defaults to false.
  • Resetting doesn't fire eunchange. Exactly like a native <input>, form.reset() changes checked without dispatching a change event. Listen for reset on the <form> itself if you need to react to it (see the demo below).
Enable notifications Subscribe to newsletter
Submit Reset
{}
<form id="my-form">
  <eun-switch name="notifications" value="enabled" default-checked>
    Enable notifications
  </eun-switch>
  <eun-switch name="newsletter" value="subscribed">
    Subscribe to newsletter
  </eun-switch>
  <button type="submit">Submit</button>
  <button type="reset">Reset</button>
</form>
const form = document.querySelector("#my-form");

// Fires on every toggle, not on reset.
form.addEventListener("eunchange", () => {
  console.log(Object.fromEntries(new FormData(form)));
});

// Fires on reset (native form event), not on eunchange. `setTimeout` lets
// the switches' own reactive update settle first, same as any other
// property change reflected asynchronously.
form.addEventListener("reset", () => {
  setTimeout(() => console.log(Object.fromEntries(new FormData(form))));
});

"Notifications" comes back on after Reset (from its default-checked), while "Newsletter" comes back off, since it never had one.

Icons

By default, the thumb shows a check icon while on and a close icon while off, rendered through eun-icon (imported separately, see the dependency badges above). Skip that import if every switch on the page sets hide-icon, or slots its own icon markup into icon-on/icon-off instead. The icons are purely decorative either way, since the state itself is already announced through aria-checked (see the Accessibility tab).

Set hide-icon to remove the icon entirely, or slot your own into icon-on/icon-off. Either way, the icon stays visible while readonly too, since that's specifically when a state cue that doesn't depend on toggling still matters most.

Default icons, on Default icons, off Icon hidden
<eun-switch checked>Default icons, on</eun-switch>
<eun-switch>Default icons, off</eun-switch>
<eun-switch hide-icon checked>Icon hidden</eun-switch>

Custom icons

icon-on/icon-off override the default glyph for their respective state. hide-icon still takes priority over either, removing the whole icon area whether it's the default glyph or a custom one.

Do not disturb
<eun-switch checked>
  Do not disturb
  <span slot="icon-on"><eun-icon name="bedtime" size="12px"></eun-icon></span>
  <span slot="icon-off"><eun-icon name="wb_sunny" size="12px"></eun-icon></span>
</eun-switch>

Custom

Override the --switch-* CSS variables, listed in full in the API tab.

Custom switch
<eun-switch class="custom-switch" checked>Custom switch</eun-switch>
.custom-switch {
  --switch-track-background-checked: #be185d;
  --switch-thumb-background: #fff;
}

Thumb size and icon color

--switch-thumb-size is independent from --switch-track-height, so sizing the thumb doesn't require resizing the whole track to match. The -checked variants (--switch-thumb-background-checked, --switch-icon-color-checked) only apply while on, falling back to the plain (off) variable otherwise, so setting just one still gives a consistent look for both states.

Custom thumb
<eun-switch class="custom-thumb" checked>Custom thumb</eun-switch>
.custom-thumb {
  --switch-thumb-size: 26px;
  --switch-thumb-background-checked: #fde68a;
  --switch-icon-color-checked: #92400e;
}

Keyboard interactions

Key Action
Tab / Shift+Tab Moves focus in/out of the switch (single tab stop)
Space Toggles checked

Aria attributes

eun-switch doesn't wrap a native <input>. The host element itself is the control, so every ARIA state lives directly on it:

  • role="switch", set on connect if not already present on the host (rather than checkbox's own default), since the action takes effect immediately instead of waiting on a later form submission
  • aria-checked="true"|"false": reflects checked
  • aria-disabled / aria-readonly: kept in sync with disabled / readonly
  • aria-required: kept in sync with required
  • aria-invalid: kept in sync with valid, once disabled or hideError are also accounted for
  • aria-description: set from hint/error content while present (via the inherited description), removed otherwise

Label accessibility

role="switch" requires an author-supplied accessible name, the same requirement checkbox's own role="checkbox" carries. eun-switch gets it from its slotted text content (<eun-switch>Enable notifications</eun-switch>, used throughout this page): the slotted text is what ends up read as the accessible name in every browser tested, relying on content-based name computation. Both the label and the track are clickable, extending the interactive area.

Label the switch with the setting it controls, not its current state. Use "Notifications", not "Notifications are on": the state itself is already announced separately via aria-checked, so baking it into the label would duplicate, and eventually contradict, that announcement once toggled.

Disabled vs. readonly

disabled removes the switch from the tab order and excludes its value from form submission entirely: it also forces the switch to render unchecked regardless of its actual checked value, since a disabled control communicating a hidden checked state serves nobody. readonly keeps it focusable and its value submitted, and additionally prevents toggling, communicating "you can't change this right now", not "this doesn't apply".

A disabled switch may cause usability and accessibility issues for people with disabilities relying on that state to still be inspectable. Prefer readonly whenever the value still needs to stay perceivable.

Icon

The check/close icon inside the thumb (see hide-icon and icon-on/icon-off in the Examples tab) is purely decorative: aria-hidden regardless of whether it's the default glyph or a slotted custom one, since the state it mirrors is already conveyed through aria-checked. It's the one visual this component keeps unmuted while readonly, on purpose. readonly already mutes the track's own color the same way disabled does (see above), so without the icon, a readonly switch's on/off state would only be legible through color contrast, not through a shape difference.

Required field

required means "must be checked", validated against the switch's actual checked state, not against its value attribute, which is typically a fixed string (e.g. value="enabled") present whether or not the switch is on, and would otherwise never look "empty" to a generic required check.

Add a visible note near the top of the form in addition to relying on the error message alone, and prefer hint to explain why toggling it on is required, not just that it is.

Screen reader restitution

By default, screen readers read out the name, on/off state, required state, and type (switch). The order may vary depending on the screen reader and its configuration. The disabled state is rendered differently depending on the screen reader:

  • VoiceOver (macOS/iOS): grayed out
  • NVDA and JAWS: unavailable
  • Narrator and TalkBack: disabled

Reference links

WAI-ARIA Authoring Practices: Switch Pattern
WAI Web Accessibility Tutorials: Labeling Controls