# Radio

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

## Overview

Radio buttons allow users to select from a set of mutually exclusive, related options.

### States

#### Size

**Example: Radio size** (`radio-overview`)

```ts radio-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqRadioModule } from '@koobiq/components/radio';

@Component({
    selector: 'radio-overview-example',
    imports: [
        KbqRadioModule
    ],
    template: `
        <div class="kbq-text-big layout-wrap">
            <div class="layout-row layout-wrap" style="gap: 24px">
                <div class="example-radio-group">
                    <div id="radio-overview-normal-label" class="layout-margin-bottom-m kbq-form__label">Normal</div>
                    <kbq-radio-group aria-labelledby="radio-overview-normal-label">
                        <kbq-radio-button [checked]="true" [value]="'option_1'">William Anderson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_2'">James Peterson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_3'">Alexander Brown</kbq-radio-button>

                        <kbq-radio-button [value]="'option_4'">Benjamin Collins</kbq-radio-button>

                        <kbq-radio-button [value]="'option_5'">Nicholas Cooper</kbq-radio-button>
                    </kbq-radio-group>
                </div>
                <div class="example-radio-group">
                    <div id="radio-overview-big-label" class="layout-margin-bottom-m kbq-form__label">Big</div>
                    <kbq-radio-group aria-labelledby="radio-overview-big-label" [big]="true">
                        <kbq-radio-button [checked]="true" [value]="'option_1'">William Anderson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_2'">James Peterson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_3'">Alexander Brown</kbq-radio-button>

                        <kbq-radio-button [value]="'option_4'">Benjamin Collins</kbq-radio-button>

                        <kbq-radio-button [value]="'option_5'">Nicholas Cooper</kbq-radio-button>
                    </kbq-radio-group>
                </div>
            </div>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class RadioOverviewExample {}
```

#### Style

**Example: Radio style** (`radio-style`)

```ts radio-style-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqComponentColors } from '@koobiq/components/core';
import { KbqRadioModule } from '@koobiq/components/radio';

@Component({
    selector: 'radio-style-example',
    imports: [
        KbqRadioModule
    ],
    template: `
        <div class="kbq-text-big layout-wrap">
            <div class="layout-row layout-wrap" style="gap: 24px">
                <div class="example-radio-group">
                    <div id="radio-style-normal-label" class="layout-margin-bottom-m kbq-form__label">Normal</div>
                    <kbq-radio-group aria-labelledby="radio-style-normal-label">
                        <kbq-radio-button [checked]="true" [value]="'option_1'">William Anderson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_2'">James Peterson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_3'">Alexander Brown</kbq-radio-button>

                        <kbq-radio-button [value]="'option_4'">Benjamin Collins</kbq-radio-button>

                        <kbq-radio-button [disabled]="true" [value]="'option_5'">Nicholas Cooper</kbq-radio-button>
                    </kbq-radio-group>
                </div>
                <div class="example-radio-group">
                    <div id="radio-style-error-label" class="layout-margin-bottom-m kbq-form__label">Error</div>
                    <kbq-radio-group aria-labelledby="radio-style-error-label" [color]="colors.Error">
                        <kbq-radio-button [checked]="true" [value]="'option_1'">William Anderson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_2'">James Peterson</kbq-radio-button>

                        <kbq-radio-button [value]="'option_3'">Alexander Brown</kbq-radio-button>

                        <kbq-radio-button [value]="'option_4'">Benjamin Collins</kbq-radio-button>

                        <kbq-radio-button [disabled]="true" [value]="'option_5'">Nicholas Cooper</kbq-radio-button>
                    </kbq-radio-group>
                </div>
            </div>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class RadioStyleExample {
    protected readonly colors = KbqComponentColors;
}
```

#### Content

**Example: Radio content** (`radio-content`)

```ts radio-content-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqFormFieldModule } from '@koobiq/components/form-field';
import { KbqRadioModule } from '@koobiq/components/radio';

@Component({
    selector: 'radio-content-example',
    imports: [
        KbqRadioModule,
        KbqFormFieldModule
    ],
    templateUrl: 'radio-content-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class RadioContentExample {
    isDisabled = false;
}
```

```html radio-content-example.html
<div class="kbq-text-big layout-wrap">
    <div style="gap: 24px" class="layout-row layout-wrap">
        <div class="example-radio-group">
            <div id="radio-content-text-label" class="layout-margin-bottom-m kbq-form__label">Text</div>
            <kbq-radio-group aria-labelledby="radio-content-text-label">
                <kbq-radio-button [checked]="true" [value]="'option_1'">William Anderson</kbq-radio-button>

                <kbq-radio-button [value]="'option_2'">James Peterson</kbq-radio-button>

                <kbq-radio-button [value]="'option_3'">Alexander Brown</kbq-radio-button>

                <kbq-radio-button [value]="'option_4'">Benjamin Collins</kbq-radio-button>

                <kbq-radio-button [disabled]="true" [value]="'option_5'">Nicholas Cooper</kbq-radio-button>
            </kbq-radio-group>
        </div>
        <div class="example-radio-group">
            <div id="radio-content-caption-label" class="layout-margin-bottom-m kbq-form__label">Text with Caption</div>
            <kbq-radio-group aria-labelledby="radio-content-caption-label">
                <kbq-radio-button [checked]="true" [value]="'option_1'">
                    William Anderson
                    <kbq-hint>Lawyer</kbq-hint>
                </kbq-radio-button>

                <kbq-radio-button [value]="'option_2'">
                    James Peterson
                    <kbq-hint>Programmer</kbq-hint>
                </kbq-radio-button>

                <kbq-radio-button [value]="'option_3'">
                    Alexander Brown
                    <kbq-hint>Biologist</kbq-hint>
                </kbq-radio-button>

                <kbq-radio-button [value]="'option_4'">
                    Benjamin Collins
                    <kbq-hint>Banker</kbq-hint>
                </kbq-radio-button>

                <kbq-radio-button [disabled]="true" [value]="'option_5'">
                    Nicholas Cooper
                    <kbq-hint>Writer</kbq-hint>
                </kbq-radio-button>
            </kbq-radio-group>
        </div>
    </div>
</div>
```

### Usage examples

#### Radio Group

**Example: Radio group** (`radio-group`)

```ts radio-group-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqRadioModule } from '@koobiq/components/radio';

@Component({
    selector: 'radio-group-example',
    imports: [
        KbqRadioModule
    ],
    template: `
        <div id="radio-group-label" class="layout-margin-bottom-m kbq-form__label">Field label</div>
        <kbq-radio-group aria-labelledby="radio-group-label">
            <kbq-radio-button [checked]="true" [value]="'option_1'">Item 1</kbq-radio-button>

            <kbq-radio-button [value]="'option_2'">Item 2</kbq-radio-button>

            <kbq-radio-button [value]="'option_3'">Item 3</kbq-radio-button>

            <kbq-radio-button [value]="'option_4'">Item 4</kbq-radio-button>

            <kbq-radio-button [disabled]="true" [value]="'option_5'">Item 5 (Disabled)</kbq-radio-button>
        </kbq-radio-group>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class RadioGroupExample {}
```

#### Multiline

**Example: Radio multiline example** (`radio-multiline`)

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

type ExampleOption = { label: string; hint: string };

@Component({
    selector: 'radio-multiline-example',
    imports: [KbqRadioModule, KbqFormFieldModule],
    template: `
        <kbq-radio-group aria-label="Security practices">
            @for (option of options; track option) {
                <kbq-radio-button [checked]="$first" [value]="$index">
                    {{ option.label }}
                    <kbq-hint>{{ option.hint }}</kbq-hint>
                </kbq-radio-button>
            }
        </kbq-radio-group>
    `,
    styles: `
        :host {
            display: flex;
            justify-content: center;
        }

        .kbq-radio-group {
            max-width: 255px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class RadioMultilineExample {
    readonly options: ExampleOption[] = [
        {
            label: 'Regular software and operating system updates',
            hint: 'Updating software and operating systems regularly helps to patch vulnerabilities and enhance security measures against potential threats'
        },
        {
            label: 'Using strong and unique passwords',
            hint: 'Creating strong and unique passwords for each online account can enhance cybersecurity, as cyber attacks often exploit weak or stolen passwords'
        },
        {
            label: 'Implementing multi-factor authentication (MFA)',
            hint: 'Multi-factor authentication involves multiple identification forms before account access, reducing the risk of unauthorized access'
        }
    ];
}
```

#### Validation

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

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

**Example: Radio validation** (`radio-validation`)

```ts radio-validation-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { KbqButtonModule } from '@koobiq/components/button';
import { ShowOnFormSubmitErrorStateMatcher } from '@koobiq/components/core';
import { KbqError, KbqHint } from '@koobiq/components/form-field';
import { KbqRadioModule } from '@koobiq/components/radio';

@Component({
    selector: 'radio-validation-example',
    imports: [ReactiveFormsModule, KbqRadioModule, KbqHint, KbqError, KbqButtonModule],
    template: `
        <form class="example-radio-validation layout-column layout-align-start-start layout-gap-l" [formGroup]="form">
            <div>
                <div id="radio-validation-label" class="layout-margin-bottom-m kbq-form__label">
                    Second sign-in factor
                </div>
                <kbq-radio-group
                    aria-labelledby="radio-validation-label"
                    formControlName="secondFactor"
                    [errorStateMatcher]="errorStateMatcher"
                >
                    <kbq-radio-button value="authenticator">Authenticator app</kbq-radio-button>
                    <kbq-radio-button value="security-key">Hardware security key</kbq-radio-button>
                    <kbq-radio-button value="sms">Code in a text message</kbq-radio-button>
                    <kbq-hint>Save without choosing to see the error</kbq-hint>
                    <kbq-error>Choose a second factor to protect the account</kbq-error>
                </kbq-radio-group>
            </div>

            <button color="contrast" kbq-button type="submit">Save</button>
        </form>
    `,
    // A fixed width keeps the centered example in place when the error, wider than the options, appears.
    styles: `
        .example-radio-validation {
            width: 320px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-align-center-center layout-row layout-padding-l'
    }
})
export class RadioValidationExample {
    protected readonly form = new FormGroup({
        secondFactor: new FormControl<string | null>(null, Validators.required)
    });
    // Shows the error only after the form is submitted, not as soon as focus leaves the group.
    protected readonly errorStateMatcher = new ShowOnFormSubmitErrorStateMatcher();
}
```

### Keyboard interaction

The component installs no key handlers of its own. The options are real `<input type="radio">`
elements sharing a `name`, so the browser supplies the whole interaction:

- A group is a **single** tab stop. `Tab` enters it at the checked option — or at the first enabled
  one when nothing is checked — and the next `Tab` leaves the group entirely.
- The arrow keys move between options **and select as they move**, so a group cannot be traversed
  without changing its value. Disabled options are skipped.
- `Space` selects the focused option.

Grouping follows `name` rather than the markup: buttons sharing a name are one tab stop even when
they sit in different containers, and a button used outside a group is named uniquely to itself and
is therefore its own tab stop.

### Accessibility

A `kbq-radio-group` is announced as a `radiogroup`, which does not take its name from the options
inside it. Point it at the visible group label instead — write `aria-labelledby` (or `aria-label`)
directly on the element, the way you would on any other host:

```html
<div id="delivery-label" class="kbq-form__label">Delivery</div>
<kbq-radio-group aria-labelledby="delivery-label">
    <kbq-radio-button [value]="'pickup'">Pickup</kbq-radio-button>
    <kbq-radio-button [value]="'courier'">Courier</kbq-radio-button>
</kbq-radio-group>
```

A `kbq-hint` projected into a button is exposed as its description, not as part of its name, so an
option is announced by its own text and the caption is left for assistive technology to defer.

`[required]` on the group reaches the `radiogroup` as `aria-required`, and a group colored with the
error palette or in the error state reaches it as `aria-invalid`. The error and the hint of the group are announced as its description, together with an `aria-describedby` you set on the
group yourself.

`focus()` on a button — or on the group, which forwards to the checked option — accepts a
`FocusOrigin`. Pass `'keyboard'` when you move focus in response to a keyboard action, so the focus
ring is visible; the default `'program'` moves focus without one.

### Name and native forms

Every group generates a unique `name`, and its buttons inherit it, so nothing has to be named by
hand. A button used outside a group falls back to an id unique to itself and is therefore
independent of every other ungrouped radio in the application.

Name the group explicitly when it has to participate in a native `<form>` submission: that name is
the field name in the payload, and a generated one changes between application loads. The button's
`value` is submitted as a string, so only string-like values round-trip.

### Recommendations

- By default, it is recommended to use up to 7 options in a Radio Group.
- Use [Button Toggle](https://koobiq.io/en/components/button-toggle.md) for short options when there is sufficient horizontal space.
- If more than 7 options need to be displayed, it is better to use [Select](https://koobiq.io/en/components/select.md).
- If the selection involves two values and can be classified as boolean, use [Toggle](https://koobiq.io/en/components/toggle.md). Toggle applies changes instantly, without additional data saving.

## API

### Components

#### KbqRadioButton (component)

```ts
@Component({ selector: 'kbq-radio-button', exportAs: 'kbqRadioButton' })
class KbqRadioButton extends KbqColorDirective {
    checked = input<boolean | undefined>();
    value = input<NonNullable<unknown> | null | undefined>();
    disabled = input<boolean | undefined>();
    required = input<boolean | undefined>();
    labelPosition = input<'before' | 'after' | undefined>();
    name = input<string | undefined>();
    readonly id = input<string>(this.uniqueId);
    readonly color = input<T | null | undefined>();
    readonly change = output<KbqRadioChange>();
    readonly inputElement: Signal<ElementRef<HTMLInputElement>>;
    radioGroup: KbqRadioGroup | null;
    readonly inputId: Signal<string>;
    get colorClassName(): string;
    focus(origin?: FocusOrigin): void;
    markForCheck(): void;
    onInputClick(event: Event): void;
    onInputChange(event: Event): void;
    setDefaultColor(color: T): void;
}
```

- `[checked]`: `boolean | undefined` — Whether this radio button is checked.
- `[value]`: `NonNullable<unknown> | null | undefined` — The value of this radio button.
- `[disabled]`: `boolean | undefined` — Whether the radio button is disabled. A button inside a disabled group is disabled as well.
- `[required]`: `boolean | undefined` — Whether the radio button is required. A button inside a required group is required as well.
- `[labelPosition]`: `'before' | 'after' | undefined` — Whether the label should appear after or before the radio button. Defaults to 'after'
- `[name]`: `string | undefined` — Analog to HTML 'name' attribute used to group radios for unique selection. Defaults to the
  parent group's name, or — outside a group — to an id unique to this button.
- `[id]`: `string` — A unique id for the radio button. If none is supplied — or `null` is bound explicitly — it is
  auto-generated, so a read always yields the id the element actually carries.
- `[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)`: `KbqRadioChange` — Event emitted when the checked state of this radio button changes.
  Change events are only emitted when the value changes due to user interaction with
  the radio button (the same behavior as `<input type-"radio">`).
- `inputElement`: `Signal<ElementRef<HTMLInputElement>>` — The native `<input type=radio>` element
- `radioGroup`: `KbqRadioGroup | null` — The parent radio group. `null` when the button is used outside one.
- `inputId`: `Signal<string>` — ID of the native input element inside `<kbq-radio-button>`
- `colorClassName`: `string` — current class name of color
- `focus()`: `void` — Focuses the radio button.

  Routed through the `FocusMonitor` so the origin is recorded: the focus ring is keyed off
  `.cdk-keyboard-focused`, which a bare `element.focus()` never produces.
- `markForCheck()`: `void` — Marks the radio button as needing checking for change detection.

  Kept for back-compatibility. A button derives its state from signals, its own and its group's, so it
  re-renders on its own and nothing in the library calls this any more.
- `setDefaultColor()`: `void` — this color will be used as a default value. For example [color]="'' | false | undefined | null".

#### KbqRadioGroup (component)

Selection group for `KbqRadioButton`.

The host is a `radiogroup`, which is not a name-from-content role, so it reaches assistive
technology unnamed unless the consumer names it. Name it with a plain `aria-label` /
`aria-labelledby` attribute pointing at the visible group label: the host is the very element the
consumer writes, so an input aliased to the same name would only write back what is already there —
and would wipe an `[attr.aria-label]` binding, which never reaches the input.

To show a validation error, bind the group to a form control and put the error text in a `kbq-error` inside the
group. A `kbq-hint` put inside the group is shown under the options, below the error.

```ts
@Component({ selector: 'kbq-radio-group', exportAs: 'kbqRadioGroup' })
class KbqRadioGroup extends KbqColorDirective implements ControlValueAccessor, CanUpdateErrorState {
    readonly errorStateMatcher = input<ErrorStateMatcher>();
    readonly big = input<boolean>(false);
    readonly name = input<string>(
        inject(_IdGenerator).getId('kbq-radio-group-')
    );
    readonly labelPosition = input<'before' | 'after'>('after');
    value = input<NonNullable<unknown> | null | undefined>();
    selected = input<KbqRadioButton | null | undefined>();
    disabled = input<boolean | undefined>();
    readonly required = input<boolean>(false);
    readonly color = input<T | null | undefined>();
    readonly change = output<KbqRadioChange>();
    readonly errorState: Signal<boolean>;
    get radios(): QueryList<KbqRadioButton>;
    get colorClassName(): string;
    checkSelectedRadioButton(): void;
    updateErrorState(): void;
    focus(origin?: FocusOrigin): void;
    touch(): void;
    emitChangeEvent(): void;
    markRadiosForCheck(): void;
    setDefaultColor(color: T): void;
}
```

- `[errorStateMatcher]`: `ErrorStateMatcher` — An object used to control when the error state is shown.
- `[name]`: `string` — Name of the radio button group. All radio buttons inside this group will use this name,
  unless they were given one of their own.
- `[labelPosition]`: `'before' | 'after'` — Whether the labels should appear after or before the radio-buttons. Defaults to 'after'
- `[value]`: `NonNullable<unknown> | null | undefined` — Value of the radio button.
- `[selected]`: `KbqRadioButton | null | undefined` — Whether the radio button is selected.
- `[disabled]`: `boolean | undefined` — Whether the radio group is disabled
- `[required]`: `boolean` — Whether the radio group is required
- `[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)`: `KbqRadioChange` — Event emitted when the group value changes.
  Change events are only emitted when the value changes due to user interaction with
  a radio button (the same behavior as `<input type-"radio">`).
- `errorState`: `Signal<boolean>` — Whether the radio group is in an error state.
- `radios`: `QueryList<KbqRadioButton>` — Child radio buttons.
- `colorClassName`: `string` — current class name of color
- `updateErrorState()`: `void` — Updates the error state based on the error state matcher.
- `focus()`: `void` — Moves focus to the checked radio button, or to the first enabled one when nothing is checked —
  the same rule the browser follows when the group is reached with `Tab`.
- `touch()`: `void` — Mark this group as being "touched" (for ngModel). Meant to be called by the contained
  radio buttons upon their blur.
- `emitChangeEvent()`: `void` — Dispatch change event with current selection and group value.
- `markRadiosForCheck()`: `void` — Marks every radio button of the group for a change-detection check.

  Kept for back-compatibility. A button derives `checked` and `disabled` from signals, its own and its
  group's, so it re-renders on its own and nothing in the library calls this any more.
- `setDefaultColor()`: `void` — this color will be used as a default value. For example [color]="'' | false | undefined | null".

### Classes

#### KbqRadioChange (class)

Change event object emitted by KbqRadio.

```ts
class KbqRadioChange {
    source: KbqRadioButton;
    value: any;
    constructor(source: KbqRadioButton, value: any);
}
```

- `source`: `KbqRadioButton` — The KbqRadioButton that emits the change event.
- `value`: `any` — The value of the KbqRadioButton.
- `constructor()`
  - `source`: `KbqRadioButton` — The KbqRadioButton that emits the change event.
  - `value`: `any` — The value of the KbqRadioButton.
