Switch
A toggle switch component for binary on/off states. Perfect for settings, feature toggles, and preferences. Built with accessibility in mind and fully customizable through CSS custom properties.
Features
Touch-Optimized — 44 × 44 px minimum touch target for mobile 6 Semantic Colors — primary, secondary, info, success, warning, error States — checked, unchecked, disabled 3 Sizes — sm, md, lg Form-Associated — participates in native form submission Customizable — CSS custom properties for styling
Source Code
View Source Code
ts
import { define, useField, html, inject, prop } from '@vielzeug/craft';
import type { CheckableProps, ComponentSize, ThemeColor } from '../../types';
import { type CheckableChangePayload, lifecycleSignal, createCheckable } from '../../headless';
import { disablableBundle, sizableBundle, SWITCH_SIZE_PRESET, themableBundle } from '../../shared';
import { colorThemeMixin, disabledStateMixin, forcedColorsFormControlMixin, sizeVariantMixin } from '../../styles';
import { applyCheckableBinding } from '../shared/field-binding';
import { FORM_CTX, useFormContext } from '../shared/form-context';
import { renderHelperRegion } from '../shared/templates';
import componentStyles from './switch.css?inline';
export type SgSwitchEvents = {
change: CheckableChangePayload;
};
export type SgSwitchProps = CheckableProps & {
/** Theme color */
color?: ThemeColor;
/** Disable interaction */
disabled?: boolean;
/** Error message (marks field as invalid) */
error?: string;
/** Helper text displayed below the switch */
helper?: string;
/** Component size */
size?: ComponentSize;
};
/**
* A toggle switch component for binary on/off states.
*
* @element sg-switch
*
* @attr {boolean} checked - Checked/on state
* @attr {boolean} disabled - Disable switch interaction
* @attr {string} value - Field value
* @attr {string} name - Form field name
* @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'
* @attr {string} size - Switch size: 'sm' | 'md' | 'lg'
* @attr {string} error - Error message (marks field as invalid)
* @attr {string} helper - Helper text displayed below the switch
*
* @fires change - Emitted when switch is toggled. detail: { checked: boolean, value: string, originalEvent?: Event }
*
* @slot - Switch label text
*
* @cssprop --switch-width - Track width
* @cssprop --switch-height - Track height
* @cssprop --switch-track-bg - Inactive track background color
* @cssprop --switch-checked-bg - Active/checked track background color
* @cssprop --switch-thumb-bg - Thumb background color
* @cssprop --switch-font-size - Label font size
* @part switch - The switch wrapper element
* @part track - The switch track element
* @part thumb - The switch thumb element
* @part label - The label element
* @part helper-text - The helper/error text element
*
* @example
* ```html
* <sg-switch name="notifications" checked color="primary">Enable notifications</sg-switch>
* <sg-switch name="darkMode" size="sm">Dark mode</sg-switch>
* <sg-switch disabled helper="Contact admin to change">Admin only</sg-switch>
* ```
*/
export const SWITCH_TAG = 'sg-switch' as const;
define<SgSwitchProps, SgSwitchEvents>(SWITCH_TAG, {
formAssociated: true,
props: {
...themableBundle,
...sizableBundle,
...disablableBundle,
checked: prop.bool(false),
error: prop.string(),
helper: prop.string(),
name: prop.string(),
value: prop.string('on'),
},
setup(props, { bind, emit, onCleanup }) {
const formCtx = inject(FORM_CTX);
const fCtxProps = useFormContext(bind, props, formCtx);
let _formField: { reportValidity(): void } | null = null;
const checkable = createCheckable({
checked: props.checked,
clearIndeterminateFirst: false,
disabled: fCtxProps.disabled,
error: props.error,
getFormField: () => _formField,
helper: props.helper,
onToggle: (payload) => {
checkable.triggerValidation('change');
emit('change', payload);
},
prefix: 'switch',
signal: lifecycleSignal(onCleanup),
validateOn: formCtx?.validateOn,
value: props.value,
});
_formField = useField<string | null>({
disabled: checkable.disabled,
toFormValue: (v) => v,
value: checkable.checkableFormValue,
});
const { assistiveId, checked, disabled, errorText, handleClick, handleKeydown, helperText, labelId } = checkable;
applyCheckableBinding(
bind,
fCtxProps.size,
{ assistiveId, checked, disabled, errorText, handleClick, handleKeydown, helperText, labelId },
'switch',
);
return html`
<div class="switch-wrapper" part="switch">
<div class="switch-track" part="track">
<div class="switch-thumb" part="thumb"></div>
</div>
</div>
<span class="label" part="label" id="${labelId}"><slot></slot></span>
${renderHelperRegion(assistiveId, errorText, helperText)}
`;
},
styles: [
colorThemeMixin,
forcedColorsFormControlMixin,
disabledStateMixin,
sizeVariantMixin(SWITCH_SIZE_PRESET),
componentStyles,
],
});Basic Usage
html
<sg-switch>Enable notifications</sg-switch>Visual Options
Colors
Six semantic colors for different contexts. Defaults to neutral when no color is specified.
Sizes
Three sizes for different contexts.
States
Checked
Toggle between on and off states.
Disabled
Prevent interaction and reduce opacity for unavailable options.
Usage Examples
Form Integration
Switches work seamlessly with forms using name and value attributes.
Event Handling
Listen to change events for custom logic.
API Reference
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Switch checked state |
disabled | boolean | false | Disable the switch |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'primary' | Semantic color |
size | 'sm' | 'md' | 'lg' | 'md' | Switch size |
name | string | - | Form field name |
value | string | - | Form field value when on |
Slots
| Slot | Description |
|---|---|
| (default) | Switch label content |
Events
| Event | Detail | Description |
|---|---|---|
change | { checked: boolean, value: string | null, originalEvent: Event } | Emitted when checked state changes |
CSS Custom Properties
| Property | Description | Default |
|---|---|---|
--switch-width | Track width | Size-dependent |
--switch-height | Track height | Size-dependent |
--switch-track-bg | Inactive (unchecked) track background | Theme-dependent |
--switch-checked-bg | Active (checked) track background | Color-dependent |
--switch-thumb-bg | Thumb background color | Theme-dependent |
--switch-font-size | Label font size | Size-dependent |
Accessibility
The switch component follows WCAG 2.1 Level AA standards.
sg-switch
Space/Entertoggle the switch.Tabmoves focus to and from the control.
- Uses
role="switch"witharia-checkedreflecting the on/off state ("true"or"false"). aria-labelledbylinks the label;aria-describedbylinks helper and error text.aria-disabledreflects the disabled state.- Minimum 44 × 44 px touch target for mobile.
Best Practices
Do:
- Use switches for instant actions that take effect immediately.
- Use clear labels that describe what the switch controls.
- Use appropriate colors (e.g., success for "enable", error for "disable critical feature").
Don't:
- Use switches when changes require a save/submit action (use checkbox instead).
- Use switches for more than two options (use radio buttons or select).
- Hide critical settings behind disabled switches without explanation.
When to Use Switch vs Checkbox
Use Switch for:
- Settings that take effect immediately
- Enabling/disabling features
- Toggling system states (on/off, true/false)
Use Checkbox for:
- Form selections that require submit
- Multiple selections in a list
- Agreeing to terms and conditions
Framework Examples
React
tsx
import '@vielzeug/sigil/switch';
function SettingsPanel() {
const [notifications, setNotifications] = useState(true);
return (
<sg-switch checked={notifications} onChange={(e) => setNotifications(e.detail.checked)}>
Enable notifications
</sg-switch>
);
}Vue
vue
<template>
<sg-switch :checked="notifications" @change="notifications = $event.detail.checked"> Enable notifications </sg-switch>
</template>
<script setup>
import { ref } from 'vue';
import '@vielzeug/sigil/switch';
const notifications = ref(true);
</script>Svelte
svelte
<script>
import '@vielzeug/sigil/switch';
let notifications = true;
</script>
<sg-switch
checked={notifications}
on:change={(e) => notifications = e.detail.checked}
>
Enable notifications
</sg-switch>