# Checkbox

- Entry point: `@koobiq/components/checkbox`
- NgModule: `KbqCheckboxModule`
- Page: https://koobiq.io/en/components/checkbox/overview

## Overview

Checkbox lets users select independent options and can also represent the selection state of a group.

### Dual-state

Dual-state is applied using the boolean attribute `[checked]` to show whether the checkbox is checked or not.

**Example: Checkbox** (`checkbox-overview`)

```ts checkbox-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqCheckboxModule } from '@koobiq/components/checkbox';
import { KbqFormFieldModule } from '@koobiq/components/form-field';

@Component({
    selector: 'checkbox-overview-example',
    imports: [
        KbqCheckboxModule,
        KbqFormFieldModule
    ],
    templateUrl: 'checkbox-overview-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class CheckboxOverviewExample {}
```

```html checkbox-overview-example.html
<div class="layout-column layout-margin-bottom-4xl">
    <kbq-checkbox class="layout-margin-bottom-m">
        <kbq-hint>kbq-hint</kbq-hint>
        Default
    </kbq-checkbox>
    <kbq-checkbox class="layout-margin-bottom-m" [checked]="true">
        <kbq-hint>kbq-hint</kbq-hint>
        Checked
    </kbq-checkbox>
    <kbq-checkbox class="layout-margin-bottom-m" [checked]="false">
        <kbq-hint>kbq-hint</kbq-hint>
        Unchecked
    </kbq-checkbox>
</div>

<div class="layout-column">
    <kbq-checkbox class="layout-margin-bottom-m" [big]="true">
        <kbq-hint>kbq-hint</kbq-hint>
        Default
    </kbq-checkbox>
    <kbq-checkbox class="layout-margin-bottom-m" [big]="true" [checked]="true">
        <kbq-hint>kbq-hint</kbq-hint>
        Checked
    </kbq-checkbox>
    <kbq-checkbox class="layout-margin-bottom-m" [big]="true" [checked]="false">
        <kbq-hint>kbq-hint</kbq-hint>
        Unchecked
    </kbq-checkbox>
</div>
```

### Indeterminate state (partial selection)

The indeterminate state is applied using the boolean attribute `[indeterminate]` and can be used when you have a group of options and a higher-level checkbox should reflect their state:

- If only some options in the group are selected, the higher-level checkbox appears partially selected (`[indeterminate]="true"`).
- If all are selected, the higher-level checkbox is shown as checked.
- If none are selected, the higher-level checkbox appears unchecked.

**Example: Checkbox indeterminate** (`checkbox-indeterminate`)

```ts checkbox-indeterminate-example.ts
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { KbqCheckboxModule } from '@koobiq/components/checkbox';

@Component({
    selector: 'checkbox-indeterminate-example',
    imports: [KbqCheckboxModule],
    template: `
        <kbq-checkbox [checked]="allSelected()" [indeterminate]="someSelected()" (change)="toggleAll()">
            All event types
        </kbq-checkbox>

        @for (eventType of eventTypes; track eventType) {
            <kbq-checkbox
                class="example-checkbox-indeterminate__child"
                [checked]="selected().has(eventType)"
                (change)="toggle(eventType)"
            >
                {{ eventType }}
            </kbq-checkbox>
        }
    `,
    styles: `
        .example-checkbox-indeterminate__child {
            margin-top: var(--kbq-size-l);
            margin-left: calc(var(--kbq-size-l) + var(--kbq-size-s));
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-column layout-align-start-start'
    }
})
export class CheckboxIndeterminateExample {
    protected readonly eventTypes = ['Malware', 'Phishing', 'Ransomware'];
    protected readonly selected = signal(new Set([this.eventTypes[0]]));

    protected readonly allSelected = computed(() => this.selected().size === this.eventTypes.length);
    protected readonly someSelected = computed(() => this.selected().size > 0 && !this.allSelected());

    protected toggleAll(): void {
        this.selected.set(this.allSelected() ? new Set() : new Set(this.eventTypes));
    }

    protected toggle(eventType: string): void {
        this.selected.update((selected) => {
            const next = new Set(selected);

            if (!next.delete(eventType)) {
                next.add(eventType);
            }

            return next;
        });
    }
}
```

### Click action config

When the user clicks on a `kbq-checkbox`, the default behavior toggles the `checked` value and sets `indeterminate` to `false`.
This behavior can be configured by [providing a new value](https://angular.io/guide/dependency-injection) for `KBQ_CHECKBOX_CLICK_ACTION` on the checkbox.

```ts
providers: [
    { provide: KBQ_CHECKBOX_CLICK_ACTION, useValue: 'check' }
];
```

Possible values: noop, check, check-indeterminate

### Pseudo checkbox

**Example: Pseudo checkbox** (`pseudo-checkbox`)

```ts pseudo-checkbox-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqPseudoCheckboxModule } from '@koobiq/components/core';

@Component({
    selector: 'pseudo-checkbox-example',
    imports: [
        KbqPseudoCheckboxModule
    ],
    template: `
        <div class="layout-column">
            <kbq-pseudo-checkbox />
            <kbq-pseudo-checkbox class="layout-margin-top-l" [state]="'indeterminate'" />
            <kbq-pseudo-checkbox class="layout-margin-top-l" [state]="'checked'" />
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PseudoCheckboxExample {}
```

### Multiline

**Example: Checkbox multiline example** (`checkbox-multiline`)

```ts checkbox-multiline-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqCheckboxModule } from '@koobiq/components/checkbox';
import { KbqFormFieldModule } from '@koobiq/components/form-field';

@Component({
    selector: 'checkbox-multiline-example',
    imports: [KbqCheckboxModule, KbqFormFieldModule],
    template: `
        <kbq-checkbox>
            I accept the security policy terms and acknowledge the responsibility to safeguard sensitive information
            <kbq-hint>
                Please review our security policy for detailed information about data protection and privacy standards.
                By checking this box, you confirm that you understand and agree to comply with all security
                requirements.
            </kbq-hint>
        </kbq-checkbox>
    `,
    styles: `
        :host {
            display: flex;
            justify-content: center;
        }

        .kbq-checkbox {
            max-width: 400px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class CheckboxMultilineExample {}
```

### Validation

To show a validation error, bind the checkbox to a form control, apply `required` or `Validators.requiredTrue`, and put the error text in a `kbq-error` inside the checkbox.

To show the error at another moment, for example only after the form is submitted, pass a matcher to the `errorStateMatcher` input. The matchers are described in the [Validation guide](https://koobiq.io/en/other/validation.md).

**Example: Checkbox validation** (`checkbox-validation`)

```ts checkbox-validation-example.ts
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqCheckboxModule } from '@koobiq/components/checkbox';
import { KbqError, KbqHint } from '@koobiq/components/form-field';

@Component({
    selector: 'checkbox-validation-example',
    imports: [FormsModule, KbqCheckboxModule, KbqHint, KbqError, KbqButtonModule],
    template: `
        <form class="example-checkbox-validation layout-column layout-align-start-start layout-gap-l">
            <kbq-checkbox name="agreement" required [(ngModel)]="agreement">
                I accept the terms of the license agreement
                <kbq-hint>Continue without accepting to see the error</kbq-hint>
                <kbq-error>Accept the terms to continue</kbq-error>
            </kbq-checkbox>

            <button color="contrast" kbq-button type="submit">Continue</button>
        </form>
    `,
    // A fixed width keeps the centered example in place whatever the width of the error that appears.
    styles: `
        .example-checkbox-validation {
            width: 320px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-align-center-center layout-row layout-padding-l'
    }
})
export class CheckboxValidationExample {
    protected readonly agreement = signal(false);
}
```

## API

### Components

#### KbqCheckbox (component)

A Koobiq checkbox component. Supports all of the functionality of an HTML5 checkbox,
and exposes a similar API. A KbqCheckbox can be either checked, unchecked, indeterminate, or
disabled. Note that all additional accessibility attributes are taken care of by the component,
so there is no need to provide them yourself. However, if you want to omit a label and still
have the checkbox be accessible, you may supply an [aria-label] input.

To show a validation error, put its text in a `kbq-error` inside the checkbox bound to a form control.

```ts
@Component({ selector: 'kbq-checkbox', exportAs: 'kbqCheckbox' })
class KbqCheckbox extends KbqColorDirective implements CanUpdateErrorState {
    readonly errorStateMatcher = input<ErrorStateMatcher>();
    readonly big = input<boolean>(false);
    readonly id = input<string>(this.uniqueId);
    readonly labelPosition = input<'before' | 'after'>('after');
    readonly name = input<string | null>(null);
    readonly value = input<string>();
    readonly clickAction = input<KbqCheckboxClickAction>(
        inject(KBQ_CHECKBOX_CLICK_ACTION, { optional: true }) || undefined
    );
    readonly required = input<boolean>(false);
    checked = input<boolean | undefined>();
    disabled = input<boolean | undefined>();
    indeterminate = model<boolean | undefined>();
    readonly color = input<T | null | undefined>();
    readonly change = output<KbqCheckboxChange>();
    readonly errorState: Signal<boolean>;
    get colorClassName(): string;
    updateErrorState(): void;
    toggle(): void;
    focus(): void;
    setDefaultColor(color: T): void;
}
```

- `[errorStateMatcher]`: `ErrorStateMatcher` — An object used to control when the error state is shown.
- `[big]`: `boolean` — Whether the checkbox uses the big size.
- `[id]`: `string` — A unique id for the checkbox input. If none is supplied — or `null` is bound explicitly — it is
  auto-generated, so a read always yields the id the element actually carries.
- `[labelPosition]`: `'before' | 'after'` — Whether the label should appear after or before the checkbox. Defaults to 'after'
- `[name]`: `string | null` — Name value will be applied to the input element if present
- `[value]`: `string` — The value attribute of the native input element
- `[clickAction]`: `KbqCheckboxClickAction` — Defines the behavior when a user clicks on the checkbox.
- `[required]`: `boolean` — Whether the checkbox is required.
- `[checked]`: `boolean | undefined` — Whether the checkbox is checked.
- `[disabled]`: `boolean | undefined` — Whether the checkbox is disabled.
- `[(indeterminate)]`: `boolean | undefined` — Whether the checkbox is indeterminate. This is also known as "mixed" mode and can be used to
  represent a checkbox with three states, e.g. a checkbox that represents a nested list of
  checkable items. Note that whenever checkbox is manually clicked, indeterminate is immediately
  set to false.
- `[color]`: `T | null | undefined` — Color of the component. A value set in code holds until the input changes, and the default color is used
  while both are unset or falsy.
- `(change)`: `KbqCheckboxChange` — Event emitted when the checkbox's `checked` value changes.
- `errorState`: `Signal<boolean>` — Whether the checkbox is in an error state.
- `colorClassName`: `string` — current class name of color
- `updateErrorState()`: `void` — Updates the error state based on the error state matcher.
- `toggle()`: `void` — Toggles the `checked` state of the checkbox.
- `focus()`: `void` — Focuses the checkbox.
- `setDefaultColor()`: `void` — this color will be used as a default value. For example [color]="'' | false | undefined | null".

### Directives

#### KbqCheckboxRequiredValidator (directive)

Validator for koobiq checkbox's required attribute in template-driven checkbox.
Current CheckboxRequiredValidator only work with `input type=checkbox` and does not
work with `kbq-checkbox`.

```ts
@Directive({
    selector: 'kbq-checkbox[required][formControlName], kbq-checkbox[required][formControl], kbq-checkbox[required][ngModel]'
})
class KbqCheckboxRequiredValidator extends CheckboxRequiredValidator {
    required: string | boolean;
}
```

### Classes

#### KbqCheckboxChange (class)

Change event object emitted by KbqCheckbox.

```ts
class KbqCheckboxChange {
    source: KbqCheckbox;
    checked: boolean;
}
```

- `source`: `KbqCheckbox` — The source KbqCheckbox of the event.
- `checked`: `boolean` — The new `checked` value of the checkbox.

### Types

#### KbqCheckboxClickAction (type)

Checkbox click action when user click on input element.
noop: Do not toggle checked or indeterminate.
check: Only toggle checked status, ignore indeterminate.
check-indeterminate: Toggle checked status, set indeterminate to false. Default behavior.
undefined: Same as `check-indeterminate`.

```ts
type KbqCheckboxClickAction = KbqCheckableClickAction;
```

### Constants

#### KBQ_CHECKBOX_CLICK_ACTION (const)

Injection token that can be used to specify the checkbox click behavior.

```ts
const KBQ_CHECKBOX_CLICK_ACTION: InjectionToken<KbqCheckboxClickAction>;
```

#### KBQ_CHECKBOX_REQUIRED_VALIDATOR (const)

```ts
const KBQ_CHECKBOX_REQUIRED_VALIDATOR: Provider;
```

## Examples

### Block checkbox

A custom checkbox-like "card" control built on the shared `KbqCheckable` primitive (`@koobiq/components/core`), showing how to reuse checkbox's click/form/a11y behavior with your own markup and styling.

**Example: Custom checkbox block** (`block-checkbox`)

```ts block-checkbox-example.ts
import { ChangeDetectionStrategy, Component, inject, model, output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KbqCheckable } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';

/**
 * EXAMPLE ONLY - not a published Koobiq component.
 *
 * A checkbox-like "card" control built directly on top of the shared `KbqCheckable` primitive.
 * Demonstrates that custom markup/styling can reuse the same click, form, and a11y behavior as
 * `kbq-checkbox` and `kbq-toggle` via `hostDirectives`, without reimplementing any of it.
 */
@Component({
    selector: 'block-checkbox-component',
    imports: [KbqIconModule],
    template: `
        <label class="example-block-checkbox__layout">
            <input
                #input
                type="checkbox"
                class="example-block-checkbox__input"
                [attr.aria-checked]="checkable.getAriaChecked()"
                [checked]="checkable.checked()"
                [disabled]="checkable.disabled()"
                [tabIndex]="checkable.effectiveTabIndex()"
                (click)="onInputClick($event)"
                (change)="$event.stopPropagation()"
            />

            <div class="example-block-checkbox__thumb">
                <ng-content select="[block-checkbox-thumb]" />
            </div>

            <div class="example-block-checkbox__body">
                <div class="example-block-checkbox__title kbq-text-normal-strong">
                    <ng-content select="[block-checkbox-title]" />
                </div>
                <div class="example-block-checkbox__description kbq-text-normal">
                    <ng-content />
                </div>
            </div>

            <span class="example-block-checkbox__indicator" aria-hidden="true">
                @if (checkable.checked()) {
                    <i kbq-icon="kbq-check-s_16" color="empty"></i>
                }
            </span>
        </label>
    `,
    styleUrl: './block-checkbox-example.css',
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'example-block-checkbox',
        '[class.example-block-checkbox_checked]': 'checkable.checked()',
        '[class.example-block-checkbox_disabled]': 'checkable.disabled()'
    },
    hostDirectives: [
        { directive: KbqCheckable, inputs: ['checked', 'disabled'] }
    ]
})
export class BlockCheckboxComponent {
    protected readonly checkable = inject(KbqCheckable, { self: true });

    /** Emitted with the new `checked` value when the card is toggled by the user. */
    readonly checkedChange = output<boolean>();

    protected onInputClick(event: Event): void {
        // See KbqCheckbox/KbqToggleComponent#onInputClick - stops the label's generated click event
        // on the native input from bubbling and firing this handler a second time.
        event.stopPropagation();

        const { shouldToggle } = this.checkable.resolveClick(undefined);

        if (shouldToggle) {
            this.checkable.toggle();
            this.checkedChange.emit(this.checkable.checked());
        }
    }
}

@Component({
    selector: 'block-checkbox-example',
    imports: [KbqIconModule, BlockCheckboxComponent, FormsModule],
    template: `
        <block-checkbox-component [(ngModel)]="checked">
            <span block-checkbox-title>Title</span>
            <div>Lorem ipsum</div>
        </block-checkbox-component>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-align-center-center layout-row'
    }
})
export class BlockCheckboxExample {
    readonly checked = model(true);
}
```
