Skip to content
Ndop UI

Switch

An on/off control that takes effect right away, such as a setting.

import { Component } from '@angular/core';
import { Label } from '@/ui/label/label';
import { Switch, SwitchThumb } from '@/ui/switch/switch';

/** A switch that turns a setting on or off right away. */
@Component({
  selector: 'example-switch-demo',
  imports: [Label, Switch, SwitchThumb],
  template: `
    <div class="flex items-center gap-2">
      <button ndSwitch id="switch-demo-airplane">
        <span ndSwitchThumb></span>
      </button>
      <label ndLabel for="switch-demo-airplane">Airplane Mode</label>
    </div>
  `,
})
export class SwitchDemoExample {}

A switch turns a setting on or off, taking effect right away, like the switches in a phone's settings. It's a native button announced as a switch, with its state as on or off, and binds with [(checked)] or any forms library. The thumb moves to the end side when it's on, so it mirrors in right-to-left layouts. A small size suits dense lists.

Installation

npx @ndop/cli add switch

Usage

import { Label } from '@/ui/label/label';
import { Switch, SwitchThumb } from '@/ui/switch/switch';
<div class="flex items-center gap-2">
  <button ndSwitch id="switch-demo-airplane">
    <span ndSwitchThumb></span>
  </button>
  <label ndLabel for="switch-demo-airplane">Airplane Mode</label>
</div>

Examples

Switch small

The small size, for dense lists of settings.

import { Component } from '@angular/core';
import { Label } from '@/ui/label/label';
import { Switch, SwitchThumb } from '@/ui/switch/switch';

/** The small size, for dense lists of settings. */
@Component({
  selector: 'example-switch-small',
  imports: [Label, Switch, SwitchThumb],
  template: `
    <div class="flex items-center gap-2">
      <button
        ndSwitch
        size="sm"
        id="switch-small-captions"
        [defaultChecked]="true"
      >
        <span ndSwitchThumb></span>
      </button>
      <label ndLabel for="switch-small-captions">Captions</label>
    </div>
  `,
})
export class SwitchSmallExample {}

Switch disabled

Disabled switches, on and off.

import { Component } from '@angular/core';
import { Label } from '@/ui/label/label';
import { Switch, SwitchThumb } from '@/ui/switch/switch';

/** Disabled switches, on and off. */
@Component({
  selector: 'example-switch-disabled',
  imports: [Label, Switch, SwitchThumb],
  template: `
    <div class="flex flex-col gap-4">
      <div class="flex items-center gap-2">
        <button ndSwitch id="switch-disabled-off" disabled>
          <span ndSwitchThumb></span>
        </button>
        <label ndLabel for="switch-disabled-off">Beta features</label>
      </div>
      <div class="flex items-center gap-2">
        <button
          ndSwitch
          id="switch-disabled-on"
          disabled
          [defaultChecked]="true"
        >
          <span ndSwitchThumb></span>
        </button>
        <label ndLabel for="switch-disabled-on">Managed by your admin</label>
      </div>
    </div>
  `,
})
export class SwitchDisabledExample {}

Switch form

A settings card of switches bound to a Signal Forms model.

Email notifications

Receive emails about new products, features and more.

Receive emails about your account security.

import { Component, signal } from '@angular/core';
import { disabled, form, FormField } from '@angular/forms/signals';
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSet,
} from '@/ui/form-field/form-field';
import { Switch, SwitchThumb } from '@/ui/switch/switch';

/**
 * Email settings as switches bound to a Signal Forms model, each saved as
 * soon as it changes; security emails can't be turned off.
 */
@Component({
  selector: 'example-switch-form',
  imports: [
    Field,
    FieldContent,
    FieldDescription,
    FieldGroup,
    FieldLabel,
    FieldLegend,
    FieldSet,
    FormField,
    Switch,
    SwitchThumb,
  ],
  template: `
    <fieldset ndFieldSet class="w-full max-w-md">
      <legend ndFieldLegend>Email notifications</legend>
      <div ndFieldGroup>
        <div ndField orientation="horizontal" class="rounded-lg border p-4">
          <div ndFieldContent>
            <label ndFieldLabel for="switch-form-marketing">
              Marketing emails
            </label>
            <p ndFieldDescription>
              Receive emails about new products, features and more.
            </p>
          </div>
          <button
            ndSwitch
            id="switch-form-marketing"
            [formField]="settings.marketing"
          >
            <span ndSwitchThumb></span>
          </button>
        </div>
        <div ndField orientation="horizontal" class="rounded-lg border p-4">
          <div ndFieldContent>
            <label ndFieldLabel for="switch-form-security">
              Security emails
            </label>
            <p ndFieldDescription>
              Receive emails about your account security.
            </p>
          </div>
          <button
            ndSwitch
            id="switch-form-security"
            [formField]="settings.security"
          >
            <span ndSwitchThumb></span>
          </button>
        </div>
      </div>
    </fieldset>
  `,
})
export class SwitchFormExample {
  protected readonly model = signal({ marketing: false, security: true });

  protected readonly settings = form(this.model, (path) => {
    disabled(path.security);
  });
}

Accessibility

Implements the Switch pattern of the WAI-ARIA Authoring Practices.

Keyboard

Keys the component handles.

Key Action
SpaceTurns it on or off.
EnterTurns it on or off.

Focus

  • Unchanged from a native button: activating it keeps focus on it.
  • Leaving it marks a bound field touched.

Right-to-left

The thumb's position is styling: the styled item moves it toward the end side, so it mirrors in right-to-left layouts.

API reference

Switch

button[ndSwitch]role switch

A switch on a native button (@ndop/primitives/switch): an on/off setting that applies at once. Bind it with [(checked)] or any forms library, name it with a label[ndLabel], and put a ndSwitchThumb inside.

Manages aria-checked, aria-required, aria-invalid.

Switch binding Type Default
[size]
Size: default, or sm for dense layouts.
'default' | 'sm''default'
[class]
Extra classes, merged last so they win.
string''
[defaultChecked]
Initial checked when it isn't bound.
booleanfalse
[disabled]
Disables it. A disabled bound field disables it too.
booleanfalse
[required]
Marks it required: aria-required="true".
booleanfalse
[invalid]
Marks it invalid when no forms library is bound; with one, the field's state decides.
booleanfalse
[id]
Element id; also the key a label[ndLabel] uses to find it.
string | undefined—
[(checked)]
Whether it's on. Two-way bindable with [(checked)].
booleanfalse
Data attribute Values
data-slotswitch
data-size —
data-state —
data-disabled —
data-invalid —

SwitchThumb

[ndSwitchThumb]

The thumb of a Switch. It moves to the end side when on, so to the left in right-to-left layouts.

SwitchThumb binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotswitch-thumb
data-state —

Agent notes

Do

  • Name it with a label that says what it controls, such as "Airplane mode".
  • Apply the change as soon as it's switched.

Don't

  • Don't use a switch in a form that's only applied when submitted; use a Checkbox.
  • Don't change the label with the state, such as "On" and "Off".