Switch

A control that allows the user to toggle between checked and unchecked.

PreviousNext
import { Component } from '@angular/core';
 
import { ZardSwitchComponent } from '../switch.component';
 
@Component({
  selector: 'zard-demo-switch',
  standalone: true,
  imports: [ZardSwitchComponent],
  template: ` <z-switch /> `,
})
export class ZardDemoSwitchDefaultComponent {}
 

Installation

1

Run the CLI

Use the CLI to add the component to your project.

npx @ngzard/ui add switch
1

Add the component files

Create the component directory structure and add the following files to your project.

switch.component.ts
switch.component.ts
import { ChangeDetectionStrategy, Component, computed, forwardRef, input, output, signal, ViewEncapsulation } from '@angular/core';
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import type { ClassValue } from 'clsx';
 
import { switchVariants, type ZardSwitchVariants } from './switch.variants';
import { mergeClasses, generateId } from '../../shared/utils/utils';
 
type OnTouchedType = () => any;
type OnChangeType = (value: any) => void;
 
@Component({
  selector: 'z-switch, [z-switch]',
  standalone: true,
  exportAs: 'zSwitch',
  template: `
    <span class="flex items-center space-x-2" (mousedown)="onSwitchChange()">
      <button [id]="zId() || uniqueId()" type="button" role="switch" [attr.data-state]="status()" [attr.aria-checked]="checked()" [disabled]="disabled()" [class]="classes()">
        <span
          [attr.data-size]="zSize()"
          [attr.data-state]="status()"
          class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 data-[size=sm]:w-4 data-[size=sm]:h-4 data-[size=sm]:data-[state=checked]:translate-x-4 data-[size=sm]:data-[state=unchecked]:translate-x-0 data-[size=lg]:w-6 data-[size=lg]:h-6 data-[size=lg]:data-[state=checked]:translate-x-6 data-[size=lg]:data-[state=unchecked]:translate-x-0"
        ></span>
      </button>
 
      <label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" [for]="zId() || uniqueId()">
        <ng-content></ng-content>
      </label>
    </span>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ZardSwitchComponent),
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
})
export class ZardSwitchComponent implements ControlValueAccessor {
  readonly checkChange = output<boolean>();
  readonly class = input<ClassValue>('');
 
  readonly zType = input<ZardSwitchVariants['zType']>('default');
  readonly zSize = input<ZardSwitchVariants['zSize']>('default');
  readonly zId = input<string>('');
 
  /* eslint-disable-next-line @typescript-eslint/no-empty-function */
  private onChange: OnChangeType = () => {};
  /* eslint-disable-next-line @typescript-eslint/no-empty-function */
  private onTouched: OnTouchedType = () => {};
 
  protected readonly classes = computed(() => mergeClasses(switchVariants({ zType: this.zType(), zSize: this.zSize() }), this.class()));
 
  protected readonly uniqueId = signal<string>(generateId('switch'));
  protected checked = signal<boolean>(true);
  protected status = computed(() => (this.checked() ? 'checked' : 'unchecked'));
  protected disabled = signal(false);
 
  writeValue(val: boolean): void {
    this.checked.set(val);
  }
 
  registerOnChange(fn: OnChangeType): void {
    this.onChange = fn;
  }
 
  registerOnTouched(fn: OnTouchedType): void {
    this.onTouched = fn;
  }
 
  onSwitchChange(): void {
    if (this.disabled()) return;
 
    this.checked.update(checked => !checked);
    this.onTouched();
    this.onChange(this.checked());
    this.checkChange.emit(this.checked());
  }
 
  setDisabledState(isDisabled: boolean): void {
    this.disabled.set(isDisabled);
  }
}
 
switch.variants.ts
switch.variants.ts
import { cva, type VariantProps } from 'class-variance-authority';
 
export const switchVariants = cva(
  'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=unchecked]:bg-input',
  {
    variants: {
      zType: {
        default: 'data-[state=checked]:bg-primary',
        destructive: 'data-[state=checked]:bg-destructive',
      },
      zSize: {
        default: 'h-6 w-11',
        sm: 'h-5 w-9',
        lg: 'h-7 w-13',
      },
    },
    defaultVariants: {
      zType: 'default',
      zSize: 'default',
    },
  },
);
 
export type ZardSwitchVariants = VariantProps<typeof switchVariants>;
 

Examples

default

import { Component } from '@angular/core';
 
import { ZardSwitchComponent } from '../switch.component';
 
@Component({
  selector: 'zard-demo-switch',
  standalone: true,
  imports: [ZardSwitchComponent],
  template: ` <z-switch /> `,
})
export class ZardDemoSwitchDefaultComponent {}
 

destructive

import { Component } from '@angular/core';
 
import { ZardSwitchComponent } from '../switch.component';
 
@Component({
  selector: 'z-demo-switch-destructive',
  standalone: true,
  imports: [ZardSwitchComponent],
  template: ` <z-switch zType="destructive" /> `,
})
export class ZardDemoSwitchDestructiveComponent {}
 

size

import { Component } from '@angular/core';
 
import { ZardSwitchComponent } from '../switch.component';
 
@Component({
  selector: 'z-demo-switch-size',
  standalone: true,
  imports: [ZardSwitchComponent],
  template: `
    <z-switch zSize="sm">Small</z-switch>
    <z-switch>Default</z-switch>
    <z-switch zSize="lg">Large</z-switch>
  `,
})
export class ZardDemoSwitchSizeComponent {}
 

disabled

import { Component } from '@angular/core';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
 
import { ZardSwitchComponent } from '../switch.component';
 
@Component({
  selector: 'z-demo-switch-disabled',
  standalone: true,
  imports: [ZardSwitchComponent, FormsModule, ReactiveFormsModule],
  template: `
    <z-switch [(ngModel)]="model" disabled>Disabled</z-switch>
    <z-switch [formControl]="checkControl">Disabled in forms</z-switch>
  `,
})
export class ZardDemoSwitchDisabledComponent {
  model = false;
  checkControl = new FormControl({ value: true, disabled: true });
}
 

API

[z-switch] Directive

z-switch is a Directive that brings you a customizable switch with minimal configuration

To get a customized switch, just pass the following props to the directive.

Property Description Type Default
[zType] switch type default|destructive default
[zSize] switch size default|sm|lg default
[zId] switch id string