Skip to content
ShineUI
Install

ToggleSwitch

Settings, on or off.

<sui-toggle-switch>SelectionStable

About

On/off switch for settings. Navy means on. A hidden native checkbox with role="switch" carries focus, Space and aria-checked; the drawn track is what the user sees. A description line explains the consequence of turning the setting on, and iconOn / iconOff paint a glyph on the knob. Form-associated: posts name=value while on.

Usage

<sui-toggle-switch label="Email me weekly summaries" name="weekly-summary" value="yes"></sui-toggle-switch> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-toggle-switch"); el.addEventListener("suiChange", (e) => console.log(e.detail)); </script>
import { SuiToggleSwitch } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiToggleSwitch label="Email me weekly summaries" name="weekly-summary" value="yes" onSuiChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiToggleSwitch } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiToggleSwitch label="Email me weekly summaries" name="weekly-summary" value="yes" @sui-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiToggleSwitch } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiToggleSwitch], template: `<sui-toggle-switch label="Email me weekly summaries" name="weekly-summary" value="yes" (suiChange)="onChange($event)"></sui-toggle-switch>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

A hidden native checkbox with role="switch" carries focus, Space toggling and aria-checked; the drawn track is what the user sees. Navy means on.

<sui-toggle-switch label="Email me weekly summaries" name="weekly-summary" value="yes"></sui-toggle-switch>

Sizes and descriptions

md is a 38 by 21 track and sm is 30 by 17. A description explains the consequence of turning the setting on and is wired through aria-describedby.

<div style="display:flex;flex-direction:column;gap:14px;align-items:flex-start"> <sui-toggle-switch label="Require MFA for all users" checked></sui-toggle-switch> <sui-toggle-switch label="Auto-send statements" description="Statements go out on the first business day of each month."></sui-toggle-switch> <sui-toggle-switch label="Lock closed periods" size="sm" checked></sui-toggle-switch> <sui-toggle-switch label="Managed by your administrator" size="sm" disabled checked></sui-toggle-switch> </div>

Knob icons and label side

icon-off and icon-on paint a Font Awesome glyph on the moving knob, spelled as on sui-toggle-button. label-pos="start" moves the label before the track, which is what a settings table needs; under dir="rtl" the label, the track and the knob all mirror.

<div style="display:flex;flex-direction:column;gap:14px;align-items:flex-start"> <sui-toggle-switch label="Dark theme" icon-off="fa-sun" icon-on="fa-moon"></sui-toggle-switch> <sui-toggle-switch id="tsw-icons" label="Show voided invoices"></sui-toggle-switch> <sui-toggle-switch label="Compact table rows" label-pos="start" checked></sui-toggle-switch> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#tsw-icons'); el.iconOff = 'fa-eye-slash'; el.iconOn = 'fa-eye'; }

States

required means the switch must be on for the form to be valid. An error replaces the help line and paints a danger ring inside the track; invalid draws the same ring without a message, as on sui-checkbox. Both set aria-invalid on the input.

<div style="display:flex;flex-direction:column;gap:14px;align-items:flex-start"> <sui-toggle-switch label="I authorize the ACH debit" required help="We debit the account on the invoice due date."></sui-toggle-switch> <sui-toggle-switch label="I authorize the ACH debit" required error="Authorization is required to save the mandate."></sui-toggle-switch> <sui-toggle-switch label="Send statements by email" invalid checked></sui-toggle-switch> </div>

Read-only

readonly keeps the switch named and in the tab order, a click or Space changes nothing, and it still posts.

<div style="display:grid;gap:14px;max-width:420px"> <sui-toggle-switch label="Two-factor sign-in" name="tfa" checked readonly description="Enforced by the workspace policy."></sui-toggle-switch> </div>

In a form

An on switch posts name=value, where value defaults to "on". An off switch posts nothing, the way a native checkbox does, and a disabled one is barred from submission.

Submit to see what posts.
<form id="tsw-form" style="display:grid;gap:14px;justify-items:start;max-width:420px"> <sui-toggle-switch name="mfa" value="required" label="Require MFA for all users" checked></sui-toggle-switch> <sui-toggle-switch name="statements" label="Auto-send statements" description="Statements go out on the first business day of each month."></sui-toggle-switch> <sui-toggle-switch name="ach" required label="I authorize the ACH debit"></sui-toggle-switch> <sui-button type="submit" variant="brand" label="Save settings"></sui-button> <output id="tsw-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Submit to see what posts.</output> </form>
// setup(root): properties and events setup(root) { const form = root.querySelector('#tsw-form'); const out = root.querySelector('#tsw-out'); form.addEventListener('submit', (e) => { e.preventDefault(); const entries = [...new FormData(form)].map(([k, v]) => `${k}=${v}`); out.textContent = entries.length ? entries.join(' ') : 'Nothing posted.'; }); }

Events

suiChange carries the new on state. The switch writes checked before it emits, so a framework binding stays in step.

Statements are off.
<div style="display:flex;flex-wrap:wrap;gap:18px;align-items:center"> <sui-toggle-switch id="tsw-ev" label="Auto-send statements"></sui-toggle-switch> <span id="tsw-ev-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Statements are off.</span> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#tsw-ev'); const log = root.querySelector('#tsw-ev-log'); el.addEventListener('suiChange', (e) => { log.textContent = e.detail ? 'Statements are on.' : 'Statements are off.'; }); }

Keyboard

KeyAction
TabMoves to the switch; the track shows the focus ring.
SpaceFlips the switch and fires suiChange.
EnterSubmits the surrounding form, as a native checkbox does.

API

<sui-toggle-switch>

Properties

PropertyAttributeTypeDefaultDescription
checkedcheckedbooleanfalseOn state. Mutable: the element toggles it on user input, then emits suiChange. Reflected, so sui-toggle-switch[checked] matches the switch as it is now.
descriptiondescriptionstringstring | undefinedSecondary line under the label explaining the consequence; wired through aria-describedby.
disableddisabledbooleanfalseDisables the switch. A disabled switch posts nothing and never holds the form.
errorerrorstringstring | undefinedError message under the switch; replaces the help line and sets aria-invalid.
helphelpstringstring | undefinedHelp text under the switch. Replaced by error when set.
iconOfficon-offstringstring | undefinedFont Awesome class shown while off, e.g. "fa-solid fa-sun".
iconOnicon-onstringstring | undefinedFont Awesome class shown while on, e.g. "fa-solid fa-moon". It travels on the knob. A style prefix is added when omitted (fa-solid). Spelled as on sui-toggle-button.
invalidinvalidbooleanfalseDanger ring on the track without a message, for a form that draws its summary elsewhere, as on sui-checkbox. error draws the same ring with its message line. Both set aria-invalid.
labellabelstringstring | undefinedVisible label.
labelPoslabel-pos"end" | "start"'end'Where the label sits in reading order: end puts it after the track and start before it, which is what a settings table needs; under dir="rtl" both mirror with the text. Any other value reads as end. The knob travels toward the inline-end when on.
namenamestringstring | undefinedForm control name. Reflected: the form entry is built from the content attribute.
readOnlyreadonlybooleanfalseDisplay only: the switch keeps its name and its tab stop but a click or Space changes nothing, and it still posts. Neither a read-only nor a disabled switch holds the form on valueMissing.
requiredrequiredbooleanfalseThe switch must be on for the surrounding form to be valid, as a consent switch is.
sizesize"md" | "sm"'md'Track size: md 38 by 21 (default), sm 30 by 17.
valuevaluestring'on'Value posted with the form while on; defaults to "on".

Events

EventDetailReactVueDescription
suiChangebooleanonSuiChange@sui-changeFires on toggle; detail is the new on state.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the (hidden) native input, as el.focus() does; the track shows the focus ring.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also