Form
Radio
Usage#
import { WDS_RADIO } from '@wds/angular/radio';Examples#
Default#
value on wds-radio-group selects the option with the same value.
import { Component } from '@angular/core';
import { WdsRadioComponent, WdsRadioGroupComponent } from '@wds/angular/radio';
@Component({
selector: 'app-radio-default-demo',
imports: [WdsRadioGroupComponent, WdsRadioComponent],
template: `
<wds-radio-group value="comfortable" aria-label="Density">
<wds-radio value="default">Default</wds-radio>
<wds-radio value="comfortable">Comfortable</wds-radio>
<wds-radio value="compact">Compact</wds-radio>
</wds-radio-group>
`,
})
export class RadioDefaultDemo {}Description#
description slot adds helper text below the label.
import { Component } from '@angular/core';
import { WdsRadioComponent, WdsRadioGroupComponent } from '@wds/angular/radio';
@Component({
selector: 'app-radio-description-demo',
imports: [WdsRadioGroupComponent, WdsRadioComponent],
template: `
<wds-radio-group value="default" aria-label="Density">
<wds-radio value="default">
Default
<span slot="description">Standard spacing for most use cases.</span>
</wds-radio>
<wds-radio value="comfortable">
Comfortable
<span slot="description">More space between elements.</span>
</wds-radio>
<wds-radio value="compact">
Compact
<span slot="description">Minimal spacing for dense layouts.</span>
</wds-radio>
</wds-radio-group>
`,
})
export class RadioDescriptionDemo {}Choice card#
variant="card" turns each option into a card: a click anywhere on it selects the option.
import { Component } from '@angular/core';
import { WdsRadioComponent, WdsRadioGroupComponent } from '@wds/angular/radio';
@Component({
selector: 'app-radio-choice-card-demo',
imports: [WdsRadioGroupComponent, WdsRadioComponent],
template: `
<wds-radio-group class="w-full max-w-sm" value="plus" aria-label="Plan">
<wds-radio variant="card" value="plus">
Plus
<span slot="description">For individuals and small teams.</span>
</wds-radio>
<wds-radio variant="card" value="pro">
Pro
<span slot="description">For growing businesses.</span>
</wds-radio>
<wds-radio variant="card" value="enterprise">
Enterprise
<span slot="description">For large teams and enterprises.</span>
</wds-radio>
</wds-radio-group>
`,
})
export class RadioChoiceCardDemo {}Fieldset#
wds-field-set with a legend and a description groups the options. wds-change reports the new value.
plan: monthly
import { Component, signal } from '@angular/core';
import { FormField, form } from '@angular/forms/signals';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsRadioGroupField } from '@wds/angular/forms';
import { WDS_RADIO } from '@wds/angular/radio';
@Component({
selector: 'app-radio-fieldset-demo',
imports: [...WDS_FIELD, ...WDS_RADIO, FormField, WdsRadioGroupField],
template: `
<div class="flex w-xs max-w-full flex-col gap-3">
<wds-field-set>
<wds-field-legend variant="label">Subscription plan</wds-field-legend>
<wds-field-description>Yearly and lifetime plans offer significant savings.</wds-field-description>
<wds-radio-group [formField]="subscriptionForm.plan">
<wds-radio value="monthly">Monthly ($9.99/month)</wds-radio>
<wds-radio value="yearly">Yearly ($99.99/year)</wds-radio>
<wds-radio value="lifetime">Lifetime ($299.99)</wds-radio>
</wds-radio-group>
</wds-field-set>
<p class="m-0 font-mono text-sm text-muted-foreground">plan: {{ subscription().plan }}</p>
</div>
`,
})
export class RadioFieldsetDemo {
protected readonly subscription = signal({ plan: 'monthly' });
protected readonly subscriptionForm = form(this.subscription);
}Disabled#
disabled on an option skips it; on the group it disables all options.
import { Component } from '@angular/core';
import { WdsRadioComponent, WdsRadioGroupComponent } from '@wds/angular/radio';
@Component({
selector: 'app-radio-disabled-demo',
imports: [WdsRadioGroupComponent, WdsRadioComponent],
template: `
<wds-radio-group value="option2" aria-label="Options">
<wds-radio value="option1" disabled>Disabled</wds-radio>
<wds-radio value="option2">Option 2</wds-radio>
<wds-radio value="option3">Option 3</wds-radio>
</wds-radio-group>
`,
})
export class RadioDisabledDemo {}Invalid#
invalid on the group shows the error state on every option.
import { Component } from '@angular/core';
import { WdsFieldDescriptionComponent, WdsFieldLegendComponent, WdsFieldSetComponent } from '@wds/angular/field';
import { WdsRadioComponent, WdsRadioGroupComponent } from '@wds/angular/radio';
@Component({
selector: 'app-radio-invalid-demo',
imports: [WdsFieldSetComponent, WdsFieldLegendComponent, WdsFieldDescriptionComponent, WdsRadioGroupComponent, WdsRadioComponent],
template: `
<wds-field-set class="w-full max-w-xs">
<wds-field-legend variant="label">Notification preferences</wds-field-legend>
<wds-field-description>Choose how you want to receive notifications.</wds-field-description>
<wds-radio-group invalid>
<wds-radio value="email">Email only</wds-radio>
<wds-radio value="sms">SMS only</wds-radio>
<wds-radio value="both">Both email and SMS</wds-radio>
</wds-radio-group>
</wds-field-set>
`,
})
export class RadioInvalidDemo {}Forms#
Integration with Angular forms. Pick the API your app uses.
Signal forms#
WdsRadioGroupField connects the group to [formField]. In wds-field the label names the group and wds-field-error describes it.
import { Component, computed, signal } from '@angular/core';
import { FormField, FormRoot, form, required } from '@angular/forms/signals';
import { WdsButtonComponent } from '@wds/angular/button';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsRadioGroupField } from '@wds/angular/forms';
import { WDS_RADIO } from '@wds/angular/radio';
@Component({
selector: 'app-radio-signal-forms-demo',
imports: [...WDS_FIELD, ...WDS_RADIO, FormField, FormRoot, WdsButtonComponent, WdsRadioGroupField],
template: `
<form class="flex w-sm max-w-full flex-col gap-4" [formRoot]="notificationsForm">
<wds-field [invalid]="showErrors()">
<wds-field-label>Notify me about...</wds-field-label>
<wds-radio-group [formField]="notificationsForm.type">
<wds-radio value="all">All new messages</wds-radio>
<wds-radio value="mentions">Direct messages and mentions</wds-radio>
<wds-radio value="none">Nothing</wds-radio>
</wds-radio-group>
<wds-field-error [errors]="showErrors() ? notificationsForm.type().errors() : []" />
</wds-field>
<wds-button type="submit" class="self-start">Submit</wds-button>
<p class="m-0 font-mono text-sm text-muted-foreground">Submitted: {{ submitted() }}</p>
</form>
`,
})
export class RadioSignalFormsDemo {
protected readonly notifications = signal({ type: '' });
protected readonly submitted = signal('—');
protected readonly notificationsForm = form(
this.notifications,
(path) => {
required(path.type, { message: 'Select a notification type.' });
},
{
submission: {
action: async (form) => {
this.submitted.set(JSON.stringify(form().value()));
},
},
}
);
/** Errors appear after the user leaves the group or submits the form. */
protected readonly showErrors = computed(() => this.notificationsForm.type().touched() && this.notificationsForm.type().invalid());
}Template-driven#
WdsRadioGroupValueAccessor makes ngModel work on wds-radio-group; required needs a selected option.
import { Component, signal } from '@angular/core';
import { FormsModule, type NgForm } from '@angular/forms';
import { WdsButtonComponent } from '@wds/angular/button';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsRadioGroupValueAccessor } from '@wds/angular/forms';
import { WDS_RADIO } from '@wds/angular/radio';
@Component({
selector: 'app-radio-template-driven-demo',
imports: [...WDS_FIELD, ...WDS_RADIO, FormsModule, WdsButtonComponent, WdsRadioGroupValueAccessor],
template: `
<form #notificationsForm="ngForm" class="flex w-sm max-w-full flex-col gap-4" (ngSubmit)="save(notificationsForm)">
<wds-field [invalid]="!!type.invalid && !!type.touched">
<wds-field-label>Notify me about...</wds-field-label>
<wds-radio-group #type="ngModel" name="type" [(ngModel)]="notifications.type" required>
<wds-radio value="all">All new messages</wds-radio>
<wds-radio value="mentions">Direct messages and mentions</wds-radio>
<wds-radio value="none">Nothing</wds-radio>
</wds-radio-group>
@if (type.invalid && type.touched) {
<wds-field-error>Select a notification type.</wds-field-error>
}
</wds-field>
<wds-button type="submit" class="self-start">Submit</wds-button>
<p class="m-0 font-mono text-sm text-muted-foreground">Submitted: {{ submitted() }}</p>
</form>
`,
})
export class RadioTemplateDrivenDemo {
protected readonly notifications = { type: '' };
protected readonly submitted = signal('—');
protected save(form: NgForm): void {
if (form.invalid) {
form.form.markAllAsTouched();
return;
}
this.submitted.set(JSON.stringify(form.value));
}
}Reactive forms#
WdsRadioGroupValueAccessor works with formControl and formControlName.
import { Component, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { WdsButtonComponent } from '@wds/angular/button';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsRadioGroupValueAccessor } from '@wds/angular/forms';
import { WDS_RADIO } from '@wds/angular/radio';
@Component({
selector: 'app-radio-reactive-forms-demo',
imports: [...WDS_FIELD, ...WDS_RADIO, ReactiveFormsModule, WdsButtonComponent, WdsRadioGroupValueAccessor],
template: `
<form class="flex w-sm max-w-full flex-col gap-4" [formGroup]="notificationsForm" (ngSubmit)="save()">
<wds-field [invalid]="type.invalid && type.touched">
<wds-field-label>Notify me about...</wds-field-label>
<wds-radio-group formControlName="type">
<wds-radio value="all">All new messages</wds-radio>
<wds-radio value="mentions">Direct messages and mentions</wds-radio>
<wds-radio value="none">Nothing</wds-radio>
</wds-radio-group>
@if (type.invalid && type.touched) {
<wds-field-error>Select a notification type.</wds-field-error>
}
</wds-field>
<wds-button type="submit" class="self-start">Submit</wds-button>
<p class="m-0 font-mono text-sm text-muted-foreground">Submitted: {{ submitted() }}</p>
</form>
`,
})
export class RadioReactiveFormsDemo {
protected readonly notificationsForm = new FormGroup({
type: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
});
protected readonly type = this.notificationsForm.controls.type;
protected readonly submitted = signal('—');
protected save(): void {
if (this.notificationsForm.invalid) {
this.notificationsForm.markAllAsTouched();
return;
}
this.submitted.set(JSON.stringify(this.notificationsForm.getRawValue()));
}
}API References#
wds-radio-group#
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| name | string | '' | |
| value | string | '' | wds-radio. |
| disabled | boolean | false | |
| required | boolean | false | |
| invalid | boolean | false | invalid on every wds-radio. |
Events
| Event | Description |
|---|---|
| wds-changeCustomEvent<WdsChangeDetail> |
Methods
| Method | Description |
|---|---|
| formResetCallback() | value (np. wyrenderowanej przez serwer). |
| formDisabledCallback(disabled: boolean) | |
| checkValidity() | true when the value is valid; otherwise fires invalid on the element. |
| reportValidity() | checkValidity(), and shows the browser validation bubble when the value is invalid. |
| setCustomValidity(message: string) | input.setCustomValidity(). |
Slots
| Slot | Description |
|---|---|
| (default) | <wds-radio> elements. |
wds-radio#
<wds-radio-group>.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| value | string | '' | |
| checked | boolean | false | |
| disabled | boolean | false | |
| invalid | boolean | false | aria-invalid. A group with invalid sets it on every option. |
| variant | WdsRadioVariant | 'default' | card is a clickable card with a border and the control on the right. |
Slots
| Slot | Description |
|---|---|
| (default) | |
| description |
CSS parts (::part)
| Part | Description |
|---|---|
| root | card variant). |
| control | |
| label | |
| description |
CSS custom properties
| Property | Description |
|---|---|
| --wds-radio-border | |
| --wds-radio-checked |
Accessibility#
Screen reader#
wds-radio-group has role="radiogroup" and each wds-radio role="radio" with aria-checked. Give the group a name: aria-label, wds-field-label in wds-field, or a legend of wds-field-set around it.
wds-radio is its name; the description slot is read as a description, not as part of the name.
invalid sets aria-invalid="true" on the group and the options, required sets aria-required="true" on the group, and a disabled option gets aria-disabled="true".
value of the selected option is submitted under name, and a form reset restores the value attribute.
Keyboard support#
| Key | Function |
|---|---|