Switch
A switch turns one setting on or off, with an immediate effect: Wi-Fi, notifications, dark theme. When the choice is saved later with a form, use a checkbox. Pair every switch with a short label that says what it controls when on. See the M3 switch guidelines.
Usage
import { createSwitch } from 'mtrl';
const toggle = createSwitch({ label: 'Wi-Fi', checked: true });
toggle.on('change', ({ checked }) => setWifi(checked));
document.body.append(toggle.element);
<m-switch checked>Wi-Fi</m-switch>
<script type="module">
const switchElement = document.querySelector('m-switch');
switchElement.addEventListener('change', (event) => setWifi(event.detail.checked));
</script>
import { useState } from 'react';
import { Switch } from 'mtrl/react';
import { setWifi } from './app';
export function Example() {
const [checked, setChecked] = useState(true);
return (
<Switch checked={checked} onChange={(event) => { setChecked(event.detail.checked); setWifi(event.detail.checked); }}>Wi-Fi</Switch>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { MSwitch } from 'mtrl/vue';
import { setWifi } from './app';
const checked = ref(true);
</script>
<template>
<MSwitch v-model="checked" @change="setWifi($event.detail.checked)">Wi-Fi</MSwitch>
</template>
<script lang="ts">
import { Switch } from 'mtrl/svelte';
import { setWifi } from './app';
let checked = $state(true);
</script>
<Switch bind:checked onchange={(event) => setWifi(event.detail.checked)}>Wi-Fi</Switch>
import { createSignal } from 'solid-js';
import { Switch } from 'mtrl/solid';
import { setWifi } from './app';
export function Example() {
const [checked, setChecked] = createSignal(true);
return (
<Switch checked={checked()} onChange={(event) => { setChecked(event.detail.checked); setWifi(event.detail.checked); }}>Wi-Fi</Switch>
);
}
Examples
Icons
M3 has three configurations: a check in the handle when on (the default), no icons
(icon: 'none'), and icons in both states, where unselectedIcon also grows the off handle to
24dp. The web component takes icon, not unselectedIcon yet.
import { createSwitch } from 'mtrl';
const toggle = createSwitch({ label: 'Dark theme', icon: 'none' });
document.body.append(toggle.element);
<m-switch icon="none">Dark theme</m-switch>
import { Switch } from 'mtrl/react';
export function Example() {
return (
<Switch icon="none">Dark theme</Switch>
);
}
<script setup lang="ts">
import { MSwitch } from 'mtrl/vue';
</script>
<template>
<MSwitch icon="none">Dark theme</MSwitch>
</template>
<script lang="ts">
import { Switch } from 'mtrl/svelte';
</script>
<Switch icon="none">Dark theme</Switch>
import { Switch } from 'mtrl/solid';
export function Example() {
return (
<Switch icon="none">Dark theme</Switch>
);
}
Supporting text
Supporting text sits under the label and describes the switch. setSupportingText(text, true)
shows it as an error, and error sets the error state with or without text.
import { createSwitch } from 'mtrl';
const toggle = createSwitch({ label: 'Back up photos', supportingText: 'Uses about 2 GB' });
document.body.append(toggle.element);
function offline() {
toggle.setSupportingText('Connect to back up');
}
<m-switch supporting-text="Uses about 2 GB">Back up photos</m-switch>
<script type="module">
const switchElement = document.querySelector('m-switch');
function offline() {
switchElement.setAttribute('supporting-text', "Connect to back up");
}
</script>
import { useState } from 'react';
import { Switch } from 'mtrl/react';
export function Example() {
const [supportingText, setSupportingText] = useState("Uses about 2 GB");
const offline = () => setSupportingText("Connect to back up");
return (
<Switch supportingText={supportingText}>Back up photos</Switch>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { MSwitch } from 'mtrl/vue';
const supportingText = ref("Uses about 2 GB");
function offline() {
supportingText.value = "Connect to back up";
}
</script>
<template>
<MSwitch :supporting-text="supportingText">Back up photos</MSwitch>
</template>
<script lang="ts">
import { Switch } from 'mtrl/svelte';
let supportingText = $state("Uses about 2 GB");
function offline() {
supportingText = "Connect to back up";
}
</script>
<Switch supportingText={supportingText}>Back up photos</Switch>
import { createSignal } from 'solid-js';
import { Switch } from 'mtrl/solid';
export function Example() {
const [supportingText, setSupportingText] = createSignal("Uses about 2 GB");
const offline = () => setSupportingText("Connect to back up");
return (
<Switch supportingText={supportingText()}>Back up photos</Switch>
);
}
labelPosition: 'end' puts the label after the switch.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
label |
string | Node |
undefined |
The label, which names the switch |
labelPosition |
'start' | 'end' |
'start' |
Which side of the switch the label sits on, in the reading direction |
checked |
boolean |
false |
Whether the switch starts on |
disabled |
boolean |
false |
Whether the switch starts disabled |
icon |
string |
a check | Icon in the selected handle, as SVG markup; 'none' for no icons |
unselectedIcon |
string |
undefined |
Icon in the unselected handle, which then grows to 24dp |
supportingText |
string |
undefined |
Text under the label, linked to the switch with aria-describedby |
error |
boolean |
false |
The error state: an error outline and aria-invalid, with or without supporting text |
name |
string |
undefined |
The input's name, for forms |
value |
string |
'on' |
The value submitted when on |
required |
boolean |
false |
Whether the form requires it on |
ariaLabel |
string |
undefined |
Accessible name when there is no visible label |
class |
string |
undefined |
Additional CSS classes |
prefix |
string |
'mtrl' |
Prefix for CSS class names |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
check() / uncheck() / toggle() |
none | SwitchComponent |
Changes the state, and emits change when the state changes |
isChecked() |
none | boolean |
Whether the switch is on |
getValue() |
none | boolean |
The on state |
setValue(value) |
value: boolean | string |
SwitchComponent |
Turns it on or off; the strings 'true' and '1' turn it on |
getValueAttribute() / setValueAttribute(value) |
value: string |
string / SwitchComponent |
The input's value attribute |
getLabel() / setLabel(text) |
text: string |
string / SwitchComponent |
The label |
setSupportingText(text, isError?) |
text: string, isError?: boolean |
SwitchComponent |
The supporting text, and the error state with it |
removeSupportingText() |
none | SwitchComponent |
Removes the supporting text |
enable() / disable() |
none | SwitchComponent |
The disabled state |
on(event, handler) / off(event, handler) |
event: string, handler: Function |
SwitchComponent |
Adds or removes a listener |
destroy() |
none | void |
Removes the switch |
Events
| Event | Description | Data |
|---|---|---|
change |
The state changed | { checked, value, nativeEvent? } |
focus / blur |
The switch gained or lost focus | FocusEvent |
nativeEvent is there when the user toggled it, not for the methods. The web component's
change carries { checked, value }.
Accessibility
- The input has
role="switch"and is named by its label; supporting text describes it througharia-describedby, and the error state setsaria-invalid. Tabfocuses it;SpaceandEntertoggle it, as the M3 switch accessibility guidance has it.- Keyboard focus draws a 0.10 state layer on the handle and a 3dp focus ring around the track; a pointer shows no ring.
- In right-to-left layouts the handle runs from right to left.
- When the label is ambiguous, set
ariaLabel: "Photo album access" rather than "Photo album".
Styling
.mtrl-switch { } /* the root */
.mtrl-switch--checked, .mtrl-switch--disabled, .mtrl-switch--error { }
.mtrl-switch--label-end, .mtrl-switch--icons { }
.mtrl-switch__container, .mtrl-switch__content, .mtrl-switch__label, .mtrl-switch__helper { }
.mtrl-switch__input { } /* the native input, over the whole switch */
.mtrl-switch__track, .mtrl-switch__thumb { }
.mtrl-switch__thumb-icon, .mtrl-switch__thumb-icon--unselected { }
Measurements
From the m3.material.io switch specs, then Compose's SwitchTokens.
| Attribute | Value |
|---|---|
| Track | 52 × 32dp, fully rounded, a 2dp outline |
| Handle | 16dp off, 24dp on or with an icon, 28dp pressed |
| Icons | 16dp; on-primary-container when on, surface-container-highest when off |
| Off | surface-container-highest track, outline outline and handle |
| On | primary track, on-primary handle |
| Interaction | The handle turns on-surface-variant (off) or primary-container (on) on hover, focus and press, over a 40dp state layer |
| Disabled | Track on-surface 12%, handle on-surface 38%; when on, a surface handle |
| Label | on-surface |
| Motion | The handle moves and resizes on the fast spatial spring |