radio group

A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.

PreviousNext
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-preview',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <z-radio-group [(value)]="selected" class="w-fit">
      <div class="flex items-center gap-3">
        <z-radio zId="r1" value="default" />
        <label z-field-label for="r1">Default</label>
      </div>
      <div class="flex items-center gap-3">
        <z-radio zId="r2" value="comfortable" />
        <label z-field-label for="r2">Comfortable</label>
      </div>
      <div class="flex items-center gap-3">
        <z-radio zId="r3" value="compact" />
        <label z-field-label for="r3">Compact</label>
      </div>
    </z-radio-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupPreviewComponent {
  selected: unknown = 'comfortable';
}

Installation

Copy
npx zard-cli@latest add radio-group

Usage

import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';
Copy
<z-radio-group [(value)]="selected">
  <z-radio value="one" />
  <z-radio value="two" />
</z-radio-group>
Copy

Examples

description

Radio group items with a description, composed from z-field, z-field-content and z-field-description.

Standard spacing for most use cases.

More space between elements.

Minimal spacing for dense layouts.

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-description',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <z-radio-group [(value)]="selected" class="w-fit">
      <div z-field zOrientation="horizontal">
        <z-radio zId="desc-r1" value="default" />
        <div z-field-content>
          <label z-field-label for="desc-r1">Default</label>
          <p z-field-description>Standard spacing for most use cases.</p>
        </div>
      </div>
      <div z-field zOrientation="horizontal">
        <z-radio zId="desc-r2" value="comfortable" />
        <div z-field-content>
          <label z-field-label for="desc-r2">Comfortable</label>
          <p z-field-description>More space between elements.</p>
        </div>
      </div>
      <div z-field zOrientation="horizontal">
        <z-radio zId="desc-r3" value="compact" />
        <div z-field-content>
          <label z-field-label for="desc-r3">Compact</label>
          <p z-field-description>Minimal spacing for dense layouts.</p>
        </div>
      </div>
    </z-radio-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupDescriptionComponent {
  selected: unknown = 'comfortable';
}

choice card

Wrap the whole z-field in a label[z-field-label] for a clickable card-style selection.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-choice-card',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <z-radio-group [(value)]="selected" class="min-w-sm">
      <label z-field-label for="plus-plan">
        <div z-field zOrientation="horizontal">
          <div z-field-content>
            <div z-field-title>Plus</div>
            <p z-field-description>For individuals and small teams.</p>
          </div>
          <z-radio zId="plus-plan" value="plus" />
        </div>
      </label>
      <label z-field-label for="pro-plan">
        <div z-field zOrientation="horizontal">
          <div z-field-content>
            <div z-field-title>Pro</div>
            <p z-field-description>For growing businesses.</p>
          </div>
          <z-radio zId="pro-plan" value="pro" />
        </div>
      </label>
      <label z-field-label for="enterprise-plan">
        <div z-field zOrientation="horizontal">
          <div z-field-content>
            <div z-field-title>Enterprise</div>
            <p z-field-description>For large teams and enterprises.</p>
          </div>
          <z-radio zId="enterprise-plan" value="enterprise" />
        </div>
      </label>
    </z-radio-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupChoiceCardComponent {
  selected: unknown = 'plus';
}

fieldset

Use fieldset[z-field-set] and legend[z-field-legend] to group radio items with a label and description.
Subscription Plan

Yearly and lifetime plans offer significant savings.

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-fieldset',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <fieldset z-field-set class="w-full max-w-xs">
      <legend z-field-legend zVariant="label">Subscription Plan</legend>
      <p z-field-description>Yearly and lifetime plans offer significant savings.</p>
      <z-radio-group [(value)]="plan">
        <div z-field zOrientation="horizontal">
          <z-radio zId="plan-monthly" value="monthly" />
          <label z-field-label for="plan-monthly" class="font-normal">Monthly ($9.99/month)</label>
        </div>
        <div z-field zOrientation="horizontal">
          <z-radio zId="plan-yearly" value="yearly" />
          <label z-field-label for="plan-yearly" class="font-normal">Yearly ($99.99/year)</label>
        </div>
        <div z-field zOrientation="horizontal">
          <z-radio zId="plan-lifetime" value="lifetime" />
          <label z-field-label for="plan-lifetime" class="font-normal">Lifetime ($299.99)</label>
        </div>
      </z-radio-group>
    </fieldset>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupFieldsetComponent {
  plan: unknown = 'monthly';
}

disabled

Set zDisabled on a single z-radio to disable that item; data-disabled on z-field drives the disabled styles.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-disabled',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <z-radio-group [(value)]="selected" class="w-fit">
      <div z-field zOrientation="horizontal" data-disabled="true">
        <z-radio zId="disabled-1" value="option1" zDisabled />
        <label z-field-label for="disabled-1" class="font-normal">Disabled</label>
      </div>
      <div z-field zOrientation="horizontal">
        <z-radio zId="disabled-2" value="option2" />
        <label z-field-label for="disabled-2" class="font-normal">Option 2</label>
      </div>
      <div z-field zOrientation="horizontal">
        <z-radio zId="disabled-3" value="option3" />
        <label z-field-label for="disabled-3" class="font-normal">Option 3</label>
      </div>
    </z-radio-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupDisabledComponent {
  selected: unknown = 'option2';
}

invalid

Set zInvalid on z-radio (which sets aria-invalid) and data-invalid on z-field to show validation errors.
Notification Preferences

Choose how you want to receive notifications.

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-invalid',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <fieldset z-field-set class="w-full max-w-xs">
      <legend z-field-legend zVariant="label">Notification Preferences</legend>
      <p z-field-description>Choose how you want to receive notifications.</p>
      <z-radio-group [(value)]="selected">
        <div z-field zOrientation="horizontal" data-invalid="true">
          <z-radio zId="invalid-email" value="email" zInvalid />
          <label z-field-label for="invalid-email" class="font-normal">Email only</label>
        </div>
        <div z-field zOrientation="horizontal" data-invalid="true">
          <z-radio zId="invalid-sms" value="sms" zInvalid />
          <label z-field-label for="invalid-sms" class="font-normal">SMS only</label>
        </div>
        <div z-field zOrientation="horizontal" data-invalid="true">
          <z-radio zId="invalid-both" value="both" zInvalid />
          <label z-field-label for="invalid-both" class="font-normal">Both Email & SMS</label>
        </div>
      </z-radio-group>
    </fieldset>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupInvalidComponent {
  selected: unknown = 'email';
}

reactive forms

Bind z-radio-group with formControlName; the disabled state of a FormControl is respected out of the box.
Billing plan
Add-ons (locked)

Plan: monthly

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';

@Component({
  selector: 'z-demo-radio-group-reactive-forms',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports, ReactiveFormsModule],
  template: `
    <form [formGroup]="form">
      <div z-field-group class="w-64">
        <fieldset z-field-set>
          <legend z-field-legend zVariant="label">Billing plan</legend>
          <z-radio-group formControlName="plan">
            <div z-field zOrientation="horizontal">
              <z-radio zId="reactive-plan-monthly" value="monthly" />
              <label z-field-label for="reactive-plan-monthly" class="font-normal">Monthly</label>
            </div>
            <div z-field zOrientation="horizontal">
              <z-radio zId="reactive-plan-yearly" value="yearly" />
              <label z-field-label for="reactive-plan-yearly" class="font-normal">Yearly</label>
            </div>
          </z-radio-group>
        </fieldset>
        <fieldset z-field-set>
          <legend z-field-legend zVariant="label">Add-ons (locked)</legend>
          <z-radio-group formControlName="addons">
            <div z-field zOrientation="horizontal" data-disabled="true">
              <z-radio zId="reactive-addons-none" value="none" />
              <label z-field-label for="reactive-addons-none" class="font-normal">None</label>
            </div>
            <div z-field zOrientation="horizontal" data-disabled="true">
              <z-radio zId="reactive-addons-priority" value="priority" />
              <label z-field-label for="reactive-addons-priority" class="font-normal">Priority support</label>
            </div>
          </z-radio-group>
        </fieldset>
        <p class="text-muted-foreground text-sm">Plan: {{ form.controls.plan.value }}</p>
      </div>
    </form>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupReactiveFormsComponent {
  private readonly fb = inject(FormBuilder);

  protected readonly form = this.fb.group({
    plan: ['monthly'],
    addons: [{ value: 'none', disabled: true }],
  });
}

rtl

To enable RTL support in zard/ui, see the RTL configuration guide.

تباعد قياسي لمعظم حالات الاستخدام.

مساحة أكبر بين العناصر.

// You will notice this example is rendered inside a wrapper that sets the dir and data-lang attributes, and
// reads its text from a translations helper. This is because this site is not RTL by default.
// In your application you won't need either: set dir="rtl" on <html> (or use the CDK Dir directive on a subtree)
// and write the text of your own language. See the RTL guide: https://zardui.com/docs/rtl

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardRadioGroupImports } from '@/shared/components/radio-group/radio-group.imports';
import { injectRtlTranslation, type ZardRtlTranslations } from '@/shared/demo-utils/rtl-translation';

const translations: ZardRtlTranslations<{
  defaultLabel: string;
  defaultDesc: string;
  comfortableLabel: string;
  comfortableDesc: string;
}> = {
  en: {
    dir: 'ltr',
    values: {
      defaultLabel: 'Default',
      defaultDesc: 'Standard spacing for most use cases.',
      comfortableLabel: 'Comfortable',
      comfortableDesc: 'More space between elements.',
    },
  },
  ar: {
    dir: 'rtl',
    values: {
      defaultLabel: 'افتراضي',
      defaultDesc: 'تباعد قياسي لمعظم حالات الاستخدام.',
      comfortableLabel: 'مريح',
      comfortableDesc: 'مساحة أكبر بين العناصر.',
    },
  },
  he: {
    dir: 'rtl',
    values: {
      defaultLabel: 'ברירת מחדל',
      defaultDesc: 'ריווח סטנדרטי לרוב המקרים.',
      comfortableLabel: 'נוח',
      comfortableDesc: 'יותר מרחב בין האלמנטים.',
    },
  },
};

@Component({
  selector: 'z-demo-radio-group-rtl',
  imports: [...ZardRadioGroupImports, ...ZardFieldImports],
  template: `
    <z-radio-group value="comfortable" class="w-fit">
      <div z-field zOrientation="horizontal">
        <z-radio zId="rtl-r1" value="default" />
        <div z-field-content>
          <label z-field-label for="rtl-r1">{{ i18n.t().defaultLabel }}</label>
          <p z-field-description>{{ i18n.t().defaultDesc }}</p>
        </div>
      </div>
      <div z-field zOrientation="horizontal">
        <z-radio zId="rtl-r2" value="comfortable" />
        <div z-field-content>
          <label z-field-label for="rtl-r2">{{ i18n.t().comfortableLabel }}</label>
          <p z-field-description>{{ i18n.t().comfortableDesc }}</p>
        </div>
      </div>
    </z-radio-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoRadioGroupRtlComponent {
  protected readonly i18n = injectRtlTranslation(translations);
}

API Reference

z-radio-groupComponent

Wrapper that groups radio items and manages the selected value. Arrow keys move the selection; ArrowLeft/ArrowRight follow the reading direction, so they swap in RTL.

PropertyDescriptionTypeDefault
[value] Selected value, two-way bindable unknown null
[(value)] Selected value (two-way binding) unknown null
[class] Additional CSS classes ClassValue ''
[zDisabled] Disables every item in the group boolean false
[name] Optional name for native form submission string ''

z-radioComponent

Individual radio button. Must be a descendant of z-radio-group.

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''
[value] Item value, compared against the group value unknown null
[zDisabled] Disables this item only boolean false
[zInvalid] Invalid state (sets aria-invalid) boolean false
[zId] Item id string -
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .