Toggle

A two-state button that can be either on or off.

PreviousNext
import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-default',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Default toggle">
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleDefaultComponent {}
 

Installation

1

Run the CLI

Use the CLI to add the component to your project.

npx @ngzard/ui@latest add toggle
1

Add the component files

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

toggle.component.ts
toggle.component.ts
import {
  ChangeDetectionStrategy,
  Component,
  forwardRef,
  ViewEncapsulation,
  signal,
  computed,
  input,
  output,
  linkedSignal,
} from '@angular/core';
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 
import type { ClassValue } from 'clsx';
 
import { toggleVariants, type ZardToggleVariants } from './toggle.variants';
import { mergeClasses, transform } from '../../shared/utils/utils';
 
type OnTouchedType = () => void;
type OnChangeType = (value: boolean) => void;
 
@Component({
  selector: 'z-toggle',
  template: `
    <button
      type="button"
      [attr.aria-label]="zAriaLabel()"
      [attr.aria-pressed]="value()"
      [attr.data-state]="value() ? 'on' : 'off'"
      [class]="classes()"
      [disabled]="disabled()"
      (click)="toggle()"
    >
      <ng-content />
    </button>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ZardToggleComponent),
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '(mouseenter)': 'handleHover()',
  },
  exportAs: 'zToggle',
})
export class ZardToggleComponent implements ControlValueAccessor {
  readonly zValue = input<boolean | undefined>();
  readonly zDefault = input<boolean>(false);
  readonly zDisabled = input(false, { alias: 'disabled', transform });
  readonly zType = input<ZardToggleVariants['zType']>('default');
  readonly zSize = input<ZardToggleVariants['zSize']>('md');
  readonly zAriaLabel = input<string>('', { alias: 'aria-label' });
  readonly class = input<ClassValue>('');
 
  readonly zToggleClick = output<void>();
  readonly zToggleHover = output<void>();
  readonly zToggleChange = output<boolean>();
 
  private readonly isUsingNgModel = signal(false);
 
  protected readonly value = linkedSignal(() => this.zValue() ?? this.zDefault());
 
  protected readonly disabled = linkedSignal(() => this.zDisabled());
 
  protected readonly classes = computed(() =>
    mergeClasses(toggleVariants({ zSize: this.zSize(), zType: this.zType() }), this.class()),
  );
 
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  private onTouched: OnTouchedType = () => {};
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  private onChangeFn: OnChangeType = () => {};
 
  handleHover() {
    this.zToggleHover.emit();
  }
 
  toggle() {
    if (this.disabled()) return;
 
    const next = !this.value();
 
    if (this.zValue() === undefined) {
      this.value.set(next);
    }
 
    this.zToggleClick.emit();
    this.zToggleChange.emit(next);
    this.onChangeFn(next);
    this.onTouched();
  }
 
  writeValue(val: boolean): void {
    this.value.set(val ?? this.zDefault());
  }
 
  registerOnChange(fn: any): void {
    this.onChangeFn = fn;
    this.isUsingNgModel.set(true);
  }
 
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
 
  setDisabledState(isDisabled: boolean): void {
    this.disabled.set(isDisabled);
  }
}
 
toggle.variants.ts
toggle.variants.ts
import { cva, type VariantProps } from 'class-variance-authority';
 
export const toggleVariants = cva(
  'inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
  {
    variants: {
      zType: {
        default: 'bg-transparent',
        outline: 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
      },
      zSize: {
        sm: 'h-8 px-2',
        md: 'h-9 px-3',
        lg: 'h-10 px-3',
      },
    },
    defaultVariants: {
      zType: 'default',
      zSize: 'md',
    },
  },
);
export type ZardToggleVariants = VariantProps<typeof toggleVariants>;
 

Examples

default

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-default',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Default toggle">
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleDefaultComponent {}
 

with forms

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-with-forms',
  imports: [ZardToggleComponent, FormsModule, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Turn on the light" [(ngModel)]="lightOn">
      @if (lightOn) {
        <z-icon zType="lightbulb" />
      } @else {
        <z-icon zType="lightbulb-off" />
      }
    </z-toggle>
  `,
})
export class ZardDemoToggleWithFormsComponent {
  protected lightOn = false;
}
 

with default

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-with-default',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="With default" [zDefault]="true">
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleWithDefaultComponent {}
 

outline

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-outline',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Toggle outline" zType="outline">
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleOutlineComponent {}
 

with text

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-with-text',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle>
      <z-icon zType="italic" />
      Italic
    </z-toggle>
  `,
})
export class ZardDemoToggleWithTextComponent {}
 

small

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-small',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Toggle small" zSize="sm">
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleSmallComponent {}
 

large

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-large',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Toggle large" zSize="lg">
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleLargeComponent {}
 

disabled

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
 
@Component({
  selector: 'z-demo-toggle-disabled',
  imports: [ZardToggleComponent, ZardIconComponent],
  standalone: true,
  template: `
    <z-toggle aria-label="Toggle disabled" disabled>
      <z-icon zType="bold" />
    </z-toggle>
  `,
})
export class ZardDemoToggleDisabledComponent {}
 

API

[z-toggle] Component

A two-state button that can be either on or off.

Properties

Property Description Type Default
[class] Custom CSS classes string ''
[zSize] Toggle size 'sm' | 'md' | 'lg' 'md'
[zType] Visual style 'default' | 'outline' 'default'
[zValue] Toggle value boolean undefined
[zDefault] Default value when uncontrolled (used as initial state only) boolean false
[disabled] Disables the toggle (also integrates with Angular Forms) boolean false
[aria-label] Accessible label for screen readers (required when using icons only) string ''

Events

Event Description Payload
(zToggleClick) Emitted when the toggle is clicked void
(zToggleHover) Emitted when the toggle is hovered void
(zToggleChange) Emitted when the toggle value changes boolean