Switch
ds-switch draws a switch around a native button: a pill two control heights
wide (32 × 16px), with a white round thumb 12px across, 2px in, travelling one
control height. Off it is grey; on it is a darker green whatever the page’s
accent, so the white thumb stays clear in light and dark. There is one switch;
there is no square one. Its measures follow the shared scale and it keeps its
focus ring visible.
<ds-switch><button type="button" role="switch" aria-checked="false" aria-label="Sharing"></button></ds-switch>The native button keeps activation for both Space and Enter, its label, ref and
disabled state, and never submits a form. Product state sets its aria-checked
value after a change is accepted.
React products import Switch from DS1/react/choice. It takes checked and
onCheckedChange; canceling its onClick prevents the change request. Disabled
and busy state are distinct: aria-busy announces work, while disabled blocks
activation when the product requires it. Use aria-label when the switch has no
visible text.
<Switch aria-label="Sharing" checked={sharing} onCheckedChange={setSharing} disabled={saving} />On Apple it is .toggleStyle(.ds1Switch), on Android DS1Switch, the same pill.
Design examples
Section titled “Design examples”Switches remain aligned at the ends of their label rows.
<section class="specimen">
<ds-row class="line" ><ds-text literal class="grow">Remember my place</ds-text><ds-switch><button type="button" role="switch" aria-label="Remember my place" aria-checked="true"></button></ds-switch></ds-row><ds-row class="line" ><ds-text literal class="grow">Show annotations</ds-text><ds-switch><button type="button" role="switch" aria-label="Show annotations" aria-checked="false"></button></ds-switch></ds-row><ds-row class="line" ><ds-text literal class="grow">Use compact rows</ds-text><ds-switch><button type="button" role="switch" aria-label="Use compact rows" aria-checked="true"></button></ds-switch></ds-row>
</section>
<style>
.specimen { width: 100%; min-width: 0; --cell: calc(20px * var(--sf, 1)); }
.specimen .line { width: 100%; }
.specimen .grow { flex: 1; min-width: 0; }
.specimen .muted { color: var(--ds-text-color-muted); }
.specimen .mono { --ds-typeface-regular: var(--ds-typeface-mono); font-variant-numeric: tabular-nums; }
.specimen .value { width: calc(4 * var(--cell)); text-align: right; }
.specimen .mark { display: inline-flex; align-items: flex-end; width: var(--cell); height: var(--ds-control-height); flex: 0 0 var(--cell); }
.specimen .columns { display: flex; flex-wrap: wrap; gap: var(--cell); }
.specimen .columns > * { min-width: 0; }
.specimen fieldset { border: 0; padding: 0; margin: 0; }
.specimen legend { padding: 0; }
.specimen label { display: flex; align-items: flex-end; gap: calc(var(--cell) / 2); min-height: var(--cell); }
.specimen ds-panel { height: calc(10 * var(--cell)); }
</style> Click or press Space to change the local setting. No settings are saved.
<label style="display:flex;align-items:center;gap:20px">Notifications <ds-switch><button type="button" role="switch" aria-label="Notifications" aria-checked="true"></button></ds-switch></label>
<label style="display:flex;align-items:center;gap:20px">Unavailable <ds-switch><button type="button" role="switch" aria-label="Unavailable" aria-checked="false" disabled></button></ds-switch></label> document.querySelectorAll('ds-toggle button').forEach(button => {
button.addEventListener('click', () => {
button.setAttribute('aria-checked', String(button.getAttribute('aria-checked') !== 'true'));
});
});