AvesraAvesrabeta

Checkbox

Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.

Import

import { AvCheckboxImports } from '@avesra/angular';

Usage

import { Component } from '@angular/core';
import { AvCheckboxImports } from '@avesra/angular';

@Component({
  selector: 'app-checkbox-basic-demo',
  imports: [AvCheckboxImports],
  template: `<div av-checkbox name="basic-terms">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>Accept terms and conditions</span>
    </div>`,
})
export class CheckboxBasicDemo {}

Anatomy

Import the Checkbox components and compose the parts with av-checkbox, av-checkbox-control, av-checkbox-indicator, and av-checkbox-content.

<div av-checkbox>
  <span av-checkbox-control>
    <span av-checkbox-indicator></span>
  </span>
  <span av-checkbox-content>
    Label <!-- plain text — the clickable label + accessible name -->
  </span>
  <p av-description></p> <!-- Optional — field-level help text -->
  <p av-field-error></p> <!-- Optional — validation message -->
</div>

Variants

Two visual styles — primary (default) and secondary.

import { Component } from '@angular/core';
import {
  AvCheckboxImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-variants-demo',
  imports: [AvCheckboxImports, AvLabelComponent],
  host: { class: 'flex w-full items-center flex-col gap-3' },
  template: `<div av-checkbox variant="primary">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>
        <label av-label class="text-sm">Primary checkbox</label>
      </span>
    </div>
    <div av-checkbox variant="secondary">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>
        <label av-label class="text-sm">Secondary checkbox</label>
      </span>
    </div>`,
})
export class CheckboxVariantsDemo {}

Disabled

import { Component } from '@angular/core';
import {
  AvCheckboxImports,
  AvDescriptionComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-disabled-demo',
  imports: [
    AvCheckboxImports,
    AvDescriptionComponent,
  ],
  template: `<div av-checkbox disabled id="feature">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>
        Premium Feature
        <p av-description>This feature is coming soon</p>
      </span>
    </div>`,
})
export class CheckboxDisabledDemo {}

External Label

Compose with av-label and matching for / id when the label lives outside the checkbox.

import { Component } from '@angular/core';
import {
  AvCheckboxImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-external-label-demo',
  imports: [
    AvCheckboxImports,
    AvLabelComponent,
  ],
  template: `<div class="flex items-center gap-3">
      <div av-checkbox id="label-marketing">
        <span av-checkbox-control>
          <span av-checkbox-indicator></span>
        </span>
        <span av-checkbox-content></span>
      </div>
      <label av-label for="label-marketing">Send me marketing emails</label>
    </div>`,
})
export class CheckboxExternalLabelDemo {}

With Description

Add av-description inside av-checkbox-content for supplementary helper text.

import { Component } from '@angular/core';
import {
  AvCheckboxImports,
  AvDescriptionComponent,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-with-description-demo',
  imports: [AvCheckboxImports, AvLabelComponent, AvDescriptionComponent],
  host: { class: 'flex w-full items-center flex-col gap-3' },
  template: `<div av-checkbox>
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>
        <label av-label class="text-sm">Accept terms and conditions</label>
        <p av-description>I agree to the terms and privacy policy</p>
      </span>
    </div>`,
})
export class CheckboxWithDescriptionDemo {}

Default Selected

Use default-selected for an initially checked uncontrolled checkbox.

import { Component } from '@angular/core';
import { AvCheckboxImports } from '@avesra/angular';

@Component({
  selector: 'app-checkbox-default-selected-demo',
  imports: [AvCheckboxImports],
  template: `<div av-checkbox default-selected id="default-notifications">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>Enable email notifications</span>
    </div>`,
})
export class CheckboxDefaultSelectedDemo {}

Invalid

Set invalid and pair with av-field-error to communicate validation failures.

import { Component } from '@angular/core';
import {
  AvCheckboxImports,
  AvFieldErrorComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-invalid-demo',
  imports: [
    AvCheckboxImports,
    AvFieldErrorComponent,
  ],
  template: `<div av-checkbox invalid name="agreement">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>I agree to the terms</span>
      <p av-field-error [visible]="true">You must accept the terms to continue</p>
    </div>`,
})
export class CheckboxInvalidDemo {}

Controlled

Bind [(selected)] for two-way control over the selected state.

Checkbox is unchecked

import { Component, signal } from '@angular/core';
import {
  AvCheckboxImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-controlled-demo',
  imports: [AvCheckboxImports, AvLabelComponent],
  host: { class: 'flex w-full items-center flex-col gap-3' },
  template: `<div av-checkbox [(selected)]="terms">
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <label av-label class="text-sm">Accept terms</label>
    </div>
    <p class="text-sm text-muted">Checkbox is {{ terms() ? 'checked' : 'unchecked' }}</p>`,
})
export class CheckboxControlledDemo {
  readonly terms = signal(false);
}

Indeterminate

Bind indeterminate for tri-state checkboxes, commonly used for “select all” patterns.

import { Component, signal } from '@angular/core';
import {
  AvCheckboxImports,
  AvDescriptionComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-indeterminate-demo',
  imports: [
    AvCheckboxImports,
    AvDescriptionComponent,
  ],
  template: `<div
      av-checkbox
      id="select-all"
      [indeterminate]="isIndeterminate()"
      [(selected)]="isSelected"
      (selectedChange)="onSelectedChange($event)"
    >
      <span av-checkbox-control>
        <span av-checkbox-indicator></span>
      </span>
      <span av-checkbox-content>
        Select all
        <p av-description>Shows indeterminate state (dash icon)</p>
      </span>
    </div>`,
})
export class CheckboxIndeterminateDemo {
  readonly isIndeterminate = signal(true);
  readonly isSelected = signal(false);

  onSelectedChange(_selected: boolean): void {
    this.isIndeterminate.set(false);
  }
}

Form Integration

Integrate with Angular reactive forms using formControlName on av-checkbox.

Form value: { "terms": false, "newsletter": true }

import { Component } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import {
  AvCheckboxImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-reactive-form-demo',
  imports: [JsonPipe, ReactiveFormsModule, AvCheckboxImports, AvLabelComponent],
  host: { class: 'flex w-full items-center flex-col gap-3' },
  template: `<form class="flex flex-col gap-4" [formGroup]="settingsForm">
      <div av-checkbox formControlName="terms">
        <span av-checkbox-control>
          <span av-checkbox-indicator></span>
        </span>
        <label av-label class="text-sm">Accept terms</label>
      </div>
      <div av-checkbox formControlName="newsletter">
        <span av-checkbox-control>
          <span av-checkbox-indicator></span>
        </span>
        <label av-label class="text-sm">Subscribe to newsletter</label>
      </div>
      <p class="text-sm text-muted">
        Form value:
        {{ settingsForm.value | json }}
      </p>
    </form>`,
})
export class CheckboxReactiveFormDemo {
  readonly settingsForm = new FormGroup({
    terms: new FormControl(false),
    newsletter: new FormControl(true),
  });
}

Reactive Form — Indeterminate

Combine reactive forms with indeterminate state for select-all patterns.

Select-all pattern: parent checkbox reflects child form controls and shows indeterminate when only some permissions are enabled.

Form value: { "read": true, "write": false, "delete": false }

Select all — selected: false, indeterminate: true

import { Component, DestroyRef, inject, signal } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { startWith } from 'rxjs';
import {
  AvCheckboxImports,
  AvDescriptionComponent,
  AvLabelComponent,
} from '@avesra/angular';

type PermissionKey = 'read' | 'write' | 'delete';

@Component({
  selector: 'app-checkbox-reactive-form-indeterminate-demo',
  imports: [JsonPipe, ReactiveFormsModule, AvCheckboxImports, AvLabelComponent, AvDescriptionComponent],
  host: { class: 'flex w-full items-center flex-col gap-3' },
  template: `<p class="text-sm text-muted">
      Select-all pattern: parent checkbox reflects child form controls and shows indeterminate
      when only some permissions are enabled.
    </p>
    <form class="flex flex-col gap-4" [formGroup]="permissionsForm">
      <div
        av-checkbox
        [selected]="selectAllSelected()"
        [indeterminate]="selectAllIndeterminate()"
        (selectedChange)="onSelectAllChange($event)"
      >
        <span av-checkbox-control>
          <span av-checkbox-indicator></span>
        </span>
        <span av-checkbox-content>
          <label av-label class="text-sm font-medium">Select all permissions</label>
          <p av-description>Indeterminate when some items are checked</p>
        </span>
      </div>

      <div class="ms-6 flex flex-col gap-3">
        <div av-checkbox formControlName="read">
          <span av-checkbox-control>
            <span av-checkbox-indicator></span>
          </span>
          <label av-label class="text-sm">Read</label>
        </div>
        <div av-checkbox formControlName="write">
          <span av-checkbox-control>
            <span av-checkbox-indicator></span>
          </span>
          <label av-label class="text-sm">Write</label>
        </div>
        <div av-checkbox formControlName="delete">
          <span av-checkbox-control>
            <span av-checkbox-indicator></span>
          </span>
          <label av-label class="text-sm">Delete</label>
        </div>
      </div>

      <p class="text-sm text-muted">
        Form value:
        {{ permissionsForm.value | json }}
      </p>
      <p class="text-sm text-muted">
        Select all — selected: {{ selectAllSelected() }}, indeterminate:
        {{ selectAllIndeterminate() }}
      </p>
    </form>`,
})
export class CheckboxReactiveFormIndeterminateDemo {
  private readonly destroyRef = inject(DestroyRef);

  readonly permissionKeys: PermissionKey[] = ['read', 'write', 'delete'];

  readonly permissionsForm = new FormGroup({
    read: new FormControl(true),
    write: new FormControl(false),
    delete: new FormControl(false),
  });

  readonly selectAllSelected = signal(false);
  readonly selectAllIndeterminate = signal(true);

  constructor() {
    this.permissionsForm.valueChanges
      .pipe(startWith(this.permissionsForm.value), takeUntilDestroyed(this.destroyRef))
      .subscribe(() => this.syncSelectAllState());
  }

  onSelectAllChange(selected: boolean): void {
    for (const key of this.permissionKeys) {
      this.permissionsForm.controls[key].setValue(selected);
    }
  }

  private syncSelectAllState(): void {
    const selectedCount = this.permissionKeys.filter(
      (key) => this.permissionsForm.controls[key].value,
    ).length;

    this.selectAllSelected.set(selectedCount === this.permissionKeys.length);
    this.selectAllIndeterminate.set(
      selectedCount > 0 && selectedCount < this.permissionKeys.length,
    );
  }
}

Validation

Drive invalid from form control state and surface errors to the user.

Valid: false · Touched: false

import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import {
  AvButtonComponent,
  AvCheckboxImports,
  AvDescriptionComponent,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-checkbox-validation-demo',
  imports: [ReactiveFormsModule, AvCheckboxImports, AvLabelComponent, AvDescriptionComponent, AvButtonComponent],
  host: { class: 'flex w-full items-center flex-col gap-3' },
  template: `<form class="flex flex-col gap-4" [formGroup]="validationForm" (ngSubmit)="onValidationSubmit()">
      <div av-checkbox formControlName="terms" [invalid]="isTermsInvalid()">
        <span av-checkbox-control>
          <span av-checkbox-indicator></span>
        </span>
        <span av-checkbox-content>
          <label av-label class="text-sm" [invalid]="isTermsInvalid()">Accept terms and conditions</label>
          <p av-description>Required to continue</p>
        </span>
      </div>
      @if (isTermsInvalid()) {
        <p class="text-sm text-danger">You must accept the terms to continue.</p>
      }
      <button av-button type="submit">Submit</button>
      <p class="text-sm text-muted">
        Valid: {{ validationForm.valid }} · Touched: {{ termsControl.touched }}
      </p>
    </form>`,
})
export class CheckboxValidationDemo {
  readonly validationForm = new FormGroup({
    terms: new FormControl(false, Validators.requiredTrue),
  });

  readonly termsControl = this.validationForm.controls.terms;

  isTermsInvalid(): boolean {
    const control = this.termsControl;
    return control.invalid && (control.touched || control.dirty);
  }

  onValidationSubmit(): void {
    this.validationForm.markAllAsTouched();

    if (this.validationForm.invalid) {
      return;
    }

    alert('Form submitted successfully!');
  }
}

Custom Indicator

Project custom SVG content into av-checkbox-indicator to replace the default checkmark.

import { Component } from '@angular/core';
import { AvCheckboxImports } from '@avesra/angular';

@Component({
  selector: 'app-checkbox-custom-indicator-demo',
  imports: [AvCheckboxImports],
  template: `<div class="flex gap-4">
      <div av-checkbox default-selected name="heart">
        <span av-checkbox-control>
          <span av-checkbox-indicator>
            <svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
              <path
                d="M12.62 20.81c-.34.12-.9.12-1.24 0C8.48 19.82 2 15.69 2 8.69 2 5.6 4.49 3.1 7.56 3.1c1.82 0 3.43.88 4.44 2.24a5.53 5.53 0 0 1 4.44-2.24C19.51 3.1 22 5.6 22 8.69c0 7-6.48 11.13-9.38 12.12Z"
                fill="currentColor"
              />
            </svg>
          </span>
        </span>
        <span av-checkbox-content>Heart</span>
      </div>
      <div av-checkbox default-selected name="plus">
        <span av-checkbox-control>
          <span av-checkbox-indicator>
            <svg fill="none" viewBox="0 0 24 24" aria-hidden="true">
              <path
                d="M6 12H18"
                stroke="currentColor"
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="3"
              />
              <path
                d="M12 18V6"
                stroke="currentColor"
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="3"
              />
            </svg>
          </span>
        </span>
        <span av-checkbox-content>Plus</span>
      </div>
      <div av-checkbox [indeterminate]="true" name="indeterminate">
        <span av-checkbox-control>
          <span av-checkbox-indicator>
            <svg stroke="currentColor" stroke-width="3" viewBox="0 0 24 24" aria-hidden="true">
              <line x1="21" x2="3" y1="12" y2="12" />
            </svg>
          </span>
        </span>
        <span av-checkbox-content>Indeterminate</span>
      </div>
    </div>`,
})
export class CheckboxCustomIndicatorDemo {}

Styling

CSS Classes

Avesra uses BEM-style classes for predictable customization.

  • .av-checkbox — Base checkbox layout
  • .av-checkbox--primary — Primary visual variant
  • .av-checkbox--secondary — Secondary visual variant
  • .av-checkbox__control — Checkbox box
  • .av-checkbox__indicator — Checkmark or indeterminate icon
  • .av-checkbox__content — Label and description container
  • .av-checkbox__input — Visually hidden native input

Interactive States

Checkbox styles support both CSS pseudo-classes and data attributes:

  • Hover::hover or [data-hovered="true"]
  • Focus::focus-visible or [data-focus-visible="true"]
  • Selected:[data-selected="true"] or [aria-checked="true"]
  • Indeterminate:[data-indeterminate="true"]
  • Pressed::active or [data-pressed="true"]
  • Disabled::disabled, [data-disabled="true"], or [aria-disabled="true"]
  • Invalid:[data-invalid="true"] or [aria-invalid="true"]

Accessibility

Checkbox uses a visually hidden native <input type="checkbox"> for semantics and provides:

  • aria-checked reflecting checked, unchecked, and indeterminate states
  • Keyboard activation via Space when focused
  • Label association through adjacent av-label or aria-label
  • aria-invalid and aria-describedby for validation feedback
  • Disabled state communicated via disabled and aria-disabled

API

PropTypeDefaultDescription
variant'primary' | 'secondary''primary'Visual style variant. Inherits from `av-checkbox-group` when omitted.
disabledbooleanfalseDisables interaction. Also set by reactive forms or parent group.
invalidbooleanfalseMarks the checkbox as invalid. Inherits from parent group when omitted.
default-selectedbooleanfalseInitial selected state for uncontrolled standalone usage.
selectedbooleanfalseSelected state for standalone usage. Supports two-way binding with `[(selected)]`.
selectedChangeEventEmitter<boolean>—Emits when the selected state changes.
indeterminatebooleanfalseIndeterminate (partially selected) state.
aria-labelstring—Accessible label when no visible label is provided.
namestring—Form field name for native form submission.
valuestring—Option value when used inside `av-checkbox-group`. Required in groups.

Made with ❤ by SyntaxHertz. Open source and available on GitHub.