Switch

A toggle switch that works with Angular forms.

import { … } from '@inlaylab/ivorie/switch'

On: false

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvSwitch } from '@inlaylab/ivorie/switch';

@Component({
  selector: 'demo-switch',
  imports: [IvSwitch],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex flex-col items-center gap-3">
      <iv-switch [(checked)]="enabled" aria-label="Notifications" />
      <p class="text-muted-foreground text-sm">On: {{ enabled() }}</p>
    </div>
  `,
})
export default class SwitchDemo {
  protected readonly enabled = signal(false);
}

Installation

CLInpm
npx ivorie add switch

A native <button role="switch"> that works as a form control. It supports ngModel, reactive forms and plain two-way binding.

Usage

import { IvSwitch } from '@inlaylab/ivorie/switch';
<iv-switch [(checked)]="enabled" aria-label="Notifications" />

Examples

With ngModel

Value: true

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IvSwitch } from '@inlaylab/ivorie/switch';

@Component({
  selector: 'demo-switch-ngmodel',
  imports: [FormsModule, IvSwitch],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex flex-col items-center gap-3">
      <iv-switch [(ngModel)]="notifications" aria-label="Notifications" />
      <p class="text-muted-foreground text-sm">Value: {{ notifications() }}</p>
    </div>
  `,
})
export default class SwitchNgmodelDemo {
  protected readonly notifications = signal(true);
}

Reactive forms

Value: false

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { IvSwitch } from '@inlaylab/ivorie/switch';

@Component({
  selector: 'demo-switch-reactive-forms',
  imports: [ReactiveFormsModule, IvSwitch],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <form [formGroup]="form" class="flex flex-col items-center gap-3">
      <iv-switch formControlName="darkMode" aria-label="Dark mode" />
      <p class="text-muted-foreground text-sm">Value: {{ form.value.darkMode }}</p>
    </form>
  `,
})
export default class SwitchReactiveFormsDemo {
  protected readonly form = new FormGroup({ darkMode: new FormControl(false) });
}

With label

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvSwitch } from '@inlaylab/ivorie/switch';

@Component({
  selector: 'demo-switch-with-label',
  imports: [IvSwitch],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <label class="flex cursor-pointer items-center gap-2 text-sm font-medium">
      <iv-switch [(checked)]="emailAlerts" />
      Email alerts
    </label>
  `,
})
export default class SwitchWithLabelDemo {
  protected readonly emailAlerts = signal(true);
}

Small

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IvSwitch } from '@inlaylab/ivorie/switch';

@Component({
  selector: 'demo-switch-small',
  imports: [IvSwitch],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex items-center gap-4">
      <iv-switch size="sm" [checked]="true" aria-label="Small" />
      <iv-switch aria-label="Default" />
    </div>
  `,
})
export default class SwitchSmallDemo {}

Disabled

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IvSwitch } from '@inlaylab/ivorie/switch';

@Component({
  selector: 'demo-switch-disabled',
  imports: [IvSwitch],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex items-center gap-4">
      <iv-switch [disabled]="true" aria-label="Off, disabled" />
      <iv-switch [checked]="true" [disabled]="true" aria-label="On, disabled" />
    </div>
  `,
})
export default class SwitchDisabledDemo {}

API

Every part also accepts class.

iv-switch

Name Type Default Description
checked boolean false On/off state. Bind with [(checked)].
size 'default' | 'sm' 'default' Track and thumb size.
disabled boolean false Blocks interaction. Also disabled when the form control is disabled.
id string | null null Set on the inner button, so <label for> works.
aria-label string | null null Set on the inner button.
aria-labelledby string | null null Set on the inner button.

Outputs:

Name Payload Description
checkedChange boolean Emits after the user toggles.

Accessibility

  • Renders <button type="button" role="switch">. The host is display: contents.
  • aria-checked follows the checked state.
  • Space and Enter toggle. Tab moves focus.
  • A wrapping <label> and <label for> both work.
  • Disabled uses the native disabled attribute.
  • An invisible padded area makes the hit target larger.

Notes

  • ngModel and formControlName need FormsModule or ReactiveFormsModule.
  • The button and thumb have data-state (checked or unchecked) and data-disabled.
  • The form marks the control touched on blur. Setting the value from the form does not emit checkedChange.