Components · Data Entry
Switch
A single setting that takes effect immediately — on or off.
Examples
Size
State
Interactivity
Children
Switch · on · off · disabled · loading · small · with checked/unchecked children
In a settings row · applies immediately
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-switch> is the SAME shared custom element React and Vue consume — here in its
native form. Theming comes only from the --aha-* tokens in tokens.css. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // registers <aha-icon> (loading spinner + children glyphs)
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-switch.js'; // registers <aha-switch>
</script>
<label style="display:inline-flex; align-items:center; gap:8px; font-family:'Plus Jakarta Sans',sans-serif; font-size:14px">
<aha-switch id="anon" checked></aha-switch>
Show results live
</label>
<!-- size="small" · loading (spinner in the knob) · checked/unchecked children (text or an icon in the track) -->
<div style="display:flex; gap:16px; align-items:center; margin-top:12px">
<aha-switch size="small" checked></aha-switch>
<aha-switch loading checked></aha-switch>
<aha-switch checked on-text="On" off-text="Off"></aha-switch>
<aha-switch checked on-icon="system-check" off-icon="system-x"></aha-switch>
</div>
<script>
document.getElementById('anon').addEventListener('change', (e) => {
console.log('live results:', e.detail.checked);
});
</script>
import '@ahaslides-product/design/icons'; // registers <aha-icon> (loading spinner + children glyphs)
import '@ahaslides-product/design/aha-switch'; // registers <aha-switch>
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper; React 19 can use <aha-switch> directly.
function AhaSwitch({ checked, disabled, loading, size, checkedChildren, unCheckedChildren, onChange }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
el.checked = !!checked;
el.disabled = !!disabled;
el.loading = !!loading;
size ? el.setAttribute('size', size) : el.removeAttribute('size');
checkedChildren != null ? el.setAttribute('on-text', checkedChildren) : el.removeAttribute('on-text');
unCheckedChildren != null ? el.setAttribute('off-text', unCheckedChildren) : el.removeAttribute('off-text');
const h = (e) => onChange?.(e.detail.checked); // composed CustomEvent
el.addEventListener('change', h);
return () => el.removeEventListener('change', h);
});
return <aha-switch ref={ref} />;
}
// usage — applies immediately, no save step
<AhaSwitch checked={live} onChange={setLive} />
<AhaSwitch size="small" checked={compact} onChange={setCompact} />
<AhaSwitch loading checked={saving} />
<AhaSwitch checkedChildren="On" unCheckedChildren="Off" checked={live} onChange={setLive} />
// main.ts — register the elements + mark aha-* as custom elements
import '@ahaslides-product/design/icons'; // registers <aha-icon> (loading spinner + children glyphs)
import '@ahaslides-product/design/aha-switch';
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref } from 'vue';
const live = ref(true);
const saving = ref(false);
</script>
<template>
<aha-switch
:checked.prop="live"
@change="live = $event.detail.checked"
/>
<!-- size="small" · loading · checked/unchecked children (text or icon) -->
<aha-switch size="small" :checked.prop="live" @change="live = $event.detail.checked" />
<aha-switch :loading.prop="saving" :checked.prop="live" />
<aha-switch on-text="On" off-text="Off" :checked.prop="live" @change="live = $event.detail.checked" />
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
checked | boolean | false | On/off state (reflected) |
disabled | boolean | false | Non-interactive |
loading | boolean | false | Spinner in the knob; blocks toggle while busy (aria-busy) |
size | 'default' | 'small' | default | Track 44×22 (default) or 28×16 (small) |
on-text | string | — | Text shown inside the track when on |
off-text | string | — | Text shown inside the track when off |
on-icon | icon name | — | DS icon shown inside the track when on (wins over on-text) |
off-icon | icon name | — | DS icon shown inside the track when off (wins over off-text) |
change | CustomEvent<{checked}> | — | Composed event on toggle; read e.detail.checked |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-switch'; // registers <aha-switch>
Agent feed for this component (absolute, fetchable anywhere): switch.agent.json · switch.md · switch.llms.txt
When to use
When to use
- Switch — a single setting that applies immediately — no save step
- Checkbox — options saved together as a group, or expressing consent
- Radio — exactly one from a small mutually-exclusive set
Never mix switches and checkboxes in one group. The label names what the setting controls as a noun phrase; the switch communicates the action — don't prefix the label with Enable/Turn on. Reach for `loading` on an async setting that may fail, `size=small` in a dense list, and checked/unchecked children only when the on/off meaning isn't obvious from context.
Surfaces
editor dashboard settings audience
Spec
Track 44×22 (small 28×16) · radius pill · 1px #D4D4D4 border · Knob 18×18 white circle, slides 20px (small 12×12, slides 12px) · Off track #F7F7F7 (gray-20) · On track #6A1EBB (primary) · Disabled 40% opacity, not-allowed · Loading system-circle-notch spinner rides in the knob; interaction blocked · Children optional text or icon inside the track, away from the knob (checked ↔ unchecked)