Checkbox

A checkbox with an indeterminate state that works with Angular forms.

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

You agree to our Terms of Service and Privacy Policy.

Get notified about sign-ins and security changes.

New features, product updates and occasional offers.

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

@Component({
  selector: 'demo-checkbox',
  imports: [IvCheckbox],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex w-full max-w-sm flex-col gap-4 text-sm">
      <div class="flex items-start gap-3">
        <iv-checkbox id="cb-terms" [(checked)]="terms" class="mt-0.5" />
        <div class="grid gap-1">
          <label for="cb-terms" class="cursor-pointer font-medium">Accept terms and conditions</label>
          <p class="text-muted-foreground">You agree to our Terms of Service and Privacy Policy.</p>
        </div>
      </div>
      <div class="flex items-start gap-3">
        <iv-checkbox id="cb-activity" [(checked)]="activity" class="mt-0.5" />
        <div class="grid gap-1">
          <label for="cb-activity" class="cursor-pointer font-medium">Account activity</label>
          <p class="text-muted-foreground">Get notified about sign-ins and security changes.</p>
        </div>
      </div>
      <div class="flex items-start gap-3">
        <iv-checkbox id="cb-marketing" [(checked)]="marketing" class="mt-0.5" />
        <div class="grid gap-1">
          <label for="cb-marketing" class="cursor-pointer font-medium">Marketing emails</label>
          <p class="text-muted-foreground">New features, product updates and occasional offers.</p>
        </div>
      </div>
    </div>
  `,
})
export default class CheckboxDemo {
  protected readonly terms = signal(false);
  protected readonly activity = signal(true);
  protected readonly marketing = signal(false);
}

Installation

CLInpm
npx ivorie add checkbox

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

Usage

import { IvCheckbox } from '@inlaylab/ivorie/checkbox';
<iv-checkbox [(checked)]="agreed" aria-label="Accept terms" />

Examples

With ngModel

Value: false

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

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

Reactive forms

Value: false

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

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

With label

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

@Component({
  selector: 'demo-checkbox-with-label',
  imports: [IvCheckbox],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <label class="flex cursor-pointer items-center gap-2 text-sm">
      <iv-checkbox [(checked)]="remember" />
      Remember me
    </label>
  `,
})
export default class CheckboxWithLabelDemo {
  protected readonly remember = signal(true);
}

Indeterminate

import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { IvCheckbox } from '@inlaylab/ivorie/checkbox';

@Component({
  selector: 'demo-checkbox-indeterminate',
  imports: [IvCheckbox],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex flex-col gap-2 text-sm">
      <label class="flex cursor-pointer items-center gap-2 font-medium">
        <iv-checkbox [checked]="all()" [indeterminate]="some()" (checkedChange)="toggleAll($event)" />
        Select all
      </label>
      @for (item of items; track item) {
        <label class="flex cursor-pointer items-center gap-2 pl-6">
          <iv-checkbox [checked]="selected().includes(item)" (checkedChange)="toggle(item)" />
          {{ item }}
        </label>
      }
    </div>
  `,
})
export default class CheckboxIndeterminateDemo {
  protected readonly items = ['Email', 'SMS', 'Push'];
  protected readonly selected = signal<string[]>(['Email']);
  protected readonly all = computed(() => this.selected().length === this.items.length);
  protected readonly some = computed(() => this.selected().length > 0 && !this.all());

  protected toggleAll(checked: boolean) {
    this.selected.set(checked ? [...this.items] : []);
  }

  protected toggle(item: string) {
    this.selected.update((s) => (s.includes(item) ? s.filter((i) => i !== item) : [...s, item]));
  }
}

Disabled

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IvCheckbox } from '@inlaylab/ivorie/checkbox';

@Component({
  selector: 'demo-checkbox-disabled',
  imports: [IvCheckbox],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="flex flex-col gap-3 text-sm">
      <label class="flex items-center gap-2">
        <iv-checkbox [disabled]="true" />
        Unchecked
      </label>
      <label class="flex items-center gap-2">
        <iv-checkbox [checked]="true" [disabled]="true" />
        Checked
      </label>
    </div>
  `,
})
export default class CheckboxDisabledDemo {}

API

Every part also accepts class.

iv-checkbox

Name Type Default Description
checked boolean false Checked state. Bind with [(checked)].
indeterminate boolean false Shows a minus icon. A click moves it to checked.
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="checkbox">. The host is display: contents.
  • aria-checked is true, false or "mixed" (indeterminate).
  • Space toggles. Enter does nothing, as in Radix. Tab moves focus.
  • A wrapping <label> and <label for> both work, because the button is labelable.
  • 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 has data-state set to checked, unchecked or indeterminate.
  • The form marks the control touched on blur. Setting the value from the form does not emit checkedChange.
  • Indeterminate click cycle: indeterminate, checked, unchecked, checked.