RadioGroup

August 8, 2025 · View on GitHub

A comprehensive radio group component with full accessibility support, multiple variants, and form integration.

Features

  • Multiple Variants: Default, destructive, success, warning, and secondary styles with variant-specific focus rings
  • Flexible Sizing: Small, default, large, and extra-large options with proportional dot sizing
  • Layout Options: Vertical (default) and horizontal orientations
  • Form Integration: Full support for Angular reactive forms with validation
  • Accessibility: Complete keyboard navigation, screen reader support, and ARIA compliance
  • Focus Management: Variant-specific focus ring colors (blue, red, green, yellow, secondary)
  • Customizable: Extensive styling options and configuration
  • TypeScript: Full type safety with TypeScript support

Installation

The RadioGroup component is part of the Angular SuperUI library:

npx ngsui-cli add radio-group

Basic Usage

Simple Radio Group

import { Component } from '@angular/core';
import { RadioGroup, RadioOption } from 'angular-superui';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [RadioGroup],
  template: `
    <RadioGroup
      [options]="frameworks"
      [(value)]="selectedFramework"
      [accessibility]="{ ariaLabel: 'Choose your preferred framework' }"
    />
  `
})
export class ExampleComponent {
  selectedFramework: string | null = null;
  
  frameworks: RadioOption[] = [
    { value: 'react', label: 'React' },
    { value: 'vue', label: 'Vue.js' },
    { value: 'angular', label: 'Angular' },
    { value: 'svelte', label: 'Svelte' }
  ];
}

With Descriptions

subscriptionPlans: RadioOption[] = [
  {
    value: 'free',
    label: 'Free',
    description: 'Perfect for personal projects and learning'
  },
  {
    value: 'pro',
    label: 'Pro',
    description: 'Best for growing teams and businesses'
  },
  {
    value: 'enterprise',
    label: 'Enterprise',
    description: 'Advanced features for large organizations'
  }
];
<RadioGroup
  [options]="subscriptionPlans"
  [(value)]="selectedPlan"
  [accessibility]="{ ariaLabel: 'Choose your subscription plan' }"
/>

Horizontal Layout

<RadioGroup
  [options]="options"
  [(value)]="selectedValue"
  orientation="horizontal"
/>

Variants

The RadioGroup component supports multiple visual variants, each with its own focus ring color for optimal accessibility and visual feedback.

Default

<RadioGroup [options]="options" variant="default" />

Focus ring: Blue (focus-visible:ring-primary)

Destructive

<RadioGroup [options]="options" variant="destructive" />

Focus ring: Red (focus-visible:ring-destructive)

Success

<RadioGroup [options]="options" variant="success" />

Focus ring: Green (focus-visible:ring-green-500)

Warning

<RadioGroup [options]="options" variant="warning" />

Focus ring: Yellow (focus-visible:ring-yellow-500)

Secondary

<RadioGroup [options]="options" variant="secondary" />

Focus ring: Secondary (focus-visible:ring-secondary)

Visual Design

Dot Sizing

The inner dots are sized proportionally to create proper padding:

  • Small (sm): 4px × 4px dot in 12px × 12px radio button
  • Default: 6px × 6px dot in 16px × 16px radio button
  • Large (lg): 8px × 8px dot in 20px × 20px radio button
  • Extra Large (xl): 10px × 10px dot in 24px × 24px radio button

Sizes

Small

<RadioGroup [options]="options" size="sm" />

Default

<RadioGroup [options]="options" size="default" />

Large

<RadioGroup [options]="options" size="lg" />

Extra Large

<RadioGroup [options]="options" size="xl" />

Form Integration

Reactive Forms

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class FormExampleComponent {
  demoForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.demoForm = this.fb.group({
      language: ['', Validators.required],
      experience: ['intermediate']
    });
  }

  onSubmit() {
    if (this.demoForm.valid) {
      console.log('Form values:', this.demoForm.value);
    }
  }
}
<form [formGroup]="demoForm" (ngSubmit)="onSubmit()">
  <RadioGroup
    formControlName="language"
    [options]="languageOptions"
    [required]="true"
    [accessibility]="{
      ariaLabel: 'Choose your primary programming language',
      ariaRequired: true
    }"
  />
  
  <div *ngIf="demoForm.get('language')?.invalid && demoForm.get('language')?.touched">
    Please select a programming language
  </div>
  
  <button type="submit" [disabled]="demoForm.invalid">Submit</button>
</form>

Template-driven Forms

<RadioGroup
  [(ngModel)]="selectedValue"
  [options]="options"
  name="selection"
  required
/>

Disabled States

Individual Options

options: RadioOption[] = [
  { value: 'enabled1', label: 'Enabled Option 1' },
  { value: 'disabled1', label: 'Disabled Option 1', disabled: true },
  { value: 'enabled2', label: 'Enabled Option 2' }
];

Entire Group

<RadioGroup
  [options]="options"
  [disabled]="true"
/>

Events

<RadioGroup
  [options]="options"
  [(value)]="selectedValue"
  (valueChange)="onValueChange($event)"
  (selectionChange)="onSelectionChange($event)"
/>
onValueChange(value: string | null): void {
  console.log('Value changed:', value);
}

onSelectionChange(option: RadioOption | null): void {
  console.log('Selection changed:', option);
}

Accessibility

The RadioGroup component provides comprehensive accessibility features:

Keyboard Navigation

  • Arrow Keys: Navigate between options
  • Home/End: Jump to first/last option
  • Space/Enter: Select the focused option

Screen Reader Support

  • Proper ARIA attributes (role, aria-checked, aria-labelledby, etc.)
  • Live region announcements for selection changes
  • Support for descriptions and validation states

Configuration

import { createRadioGroupAccessibility } from 'angular-superui';

// Create accessibility configuration
const accessibility = createRadioGroupAccessibility({
  ariaLabel: 'Choose your preference',
  ariaRequired: true,
  ariaInvalid: false,
  ariaLive: 'polite'
});
<RadioGroup
  [options]="options"
  [accessibility]="accessibility"
/>

Custom Styling

CSS Classes

<RadioGroup
  [options]="options"
  className="p-4 bg-muted rounded-lg"
/>

Individual Option Styling

styledOptions: RadioOption[] = [
  {
    value: 'option1',
    label: 'Styled Option 1',
    className: 'text-blue-600 dark:text-blue-400'
  },
  {
    value: 'option2',
    label: 'Styled Option 2',
    className: 'text-green-600 dark:text-green-400'
  }
];

API Reference

RadioGroup Props

PropTypeDefaultDescription
valuestring | nullnullCurrently selected value
optionsRadioOption[][]Array of radio options
disabledbooleanfalseWhether the entire group is disabled
requiredbooleanfalseWhether selection is required
namestringundefinedForm control name
variant'default' | 'destructive' | 'success' | 'warning' | 'secondary''default'Visual variant with specific focus ring colors
size'sm' | 'default' | 'lg' | 'xl''default'Size variant affecting radio button and dot dimensions
orientation'vertical' | 'horizontal''vertical'Layout orientation
classNamestringundefinedAdditional CSS classes
accessibilityRadioGroupAccessibility{}Accessibility configuration

RadioGroup Events

EventTypeDescription
valueChangeEventEmitter<string | null>Emitted when value changes
selectionChangeEventEmitter<RadioOption | null>Emitted when selection changes

RadioOption Interface

interface RadioOption {
  value: string;           // Unique value for the option
  label: string;           // Display label
  description?: string;    // Optional description text
  disabled?: boolean;      // Whether option is disabled
  className?: string;      // Additional CSS classes for styling individual options
}

RadioGroupAccessibility Interface

interface RadioGroupAccessibility {
  ariaLabel?: string;           // ARIA label for the group
  ariaLabelledBy?: string;      // Element ID that labels the group
  ariaDescribedBy?: string;     // Element ID that describes the group
  ariaRequired?: boolean;       // Whether selection is required
  ariaInvalid?: boolean;        // Whether group has validation errors
  ariaLive?: 'off' | 'polite' | 'assertive'; // Live region behavior for announcements
}

Focus Ring Colors by Variant

VariantFocus Ring ColorCSS Class
defaultBluefocus-visible:ring-primary
destructiveRedfocus-visible:ring-destructive
successGreenfocus-visible:ring-green-500
warningYellowfocus-visible:ring-yellow-500
secondarySecondaryfocus-visible:ring-secondary

Best Practices

Accessibility

  • Always provide meaningful labels using ariaLabel or ariaLabelledBy
  • Use descriptions for complex options
  • Set ariaRequired for required fields
  • Update ariaInvalid based on validation state

User Experience

  • Use horizontal layout for short lists (3-4 options)
  • Use vertical layout for longer lists or when options have descriptions
  • Group related options logically
  • Provide clear, concise labels

Performance

  • Use trackBy functions for dynamic option lists
  • Avoid frequent option array recreation
  • Consider virtual scrolling for very large option lists

Styling

  • Maintain consistent variant usage across your application
  • Use appropriate sizes based on context
  • Ensure sufficient color contrast for all variants
  • Test with different themes and accessibility settings

Examples

Complete Form Example

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { RadioGroup, RadioOption, createRadioGroupAccessibility } from 'angular-superui';

@Component({
  selector: 'app-survey-form',
  standalone: true,
  imports: [RadioGroup, ReactiveFormsModule],
  template: `
    <form [formGroup]="surveyForm" (ngSubmit)="onSubmit()">
      <div class="space-y-6">
        <!-- Experience Level -->
        <div class="space-y-2">
          <label class="text-sm font-medium">
            Experience Level <span class="text-red-500">*</span>
          </label>
          <RadioGroup
            formControlName="experience"
            [options]="experienceOptions"
            [required]="true"
            [accessibility]="experienceAccessibility"
            orientation="horizontal"
          />
        </div>

        <!-- Preferred Technology -->
        <div class="space-y-2">
          <label class="text-sm font-medium">Preferred Technology</label>
          <RadioGroup
            formControlName="technology"
            [options]="technologyOptions"
            [accessibility]="technologyAccessibility"
          />
        </div>

        <!-- Submit Button -->
        <button 
          type="submit" 
          [disabled]="surveyForm.invalid"
          class="px-4 py-2 bg-primary text-white rounded disabled:opacity-50"
        >
          Submit Survey
        </button>
      </div>
    </form>
  `
})
export class SurveyFormComponent {
  surveyForm: FormGroup;

  experienceOptions: RadioOption[] = [
    { value: 'beginner', label: 'Beginner' },
    { value: 'intermediate', label: 'Intermediate' },
    { value: 'advanced', label: 'Advanced' },
    { value: 'expert', label: 'Expert' }
  ];

  technologyOptions: RadioOption[] = [
    {
      value: 'frontend',
      label: 'Frontend Development',
      description: 'React, Vue, Angular, etc.'
    },
    {
      value: 'backend',
      label: 'Backend Development',
      description: 'Node.js, Python, Java, etc.'
    },
    {
      value: 'fullstack',
      label: 'Full Stack Development',
      description: 'Both frontend and backend'
    },
    {
      value: 'mobile',
      label: 'Mobile Development',
      description: 'iOS, Android, React Native, etc.'
    }
  ];

  experienceAccessibility = createRadioGroupAccessibility({
    ariaLabel: 'Select your programming experience level',
    ariaRequired: true
  });

  technologyAccessibility = createRadioGroupAccessibility({
    ariaLabel: 'Select your preferred technology area'
  });

  constructor(private fb: FormBuilder) {
    this.surveyForm = this.fb.group({
      experience: ['', Validators.required],
      technology: ['frontend']
    });
  }

  onSubmit(): void {
    if (this.surveyForm.valid) {
      console.log('Survey submitted:', this.surveyForm.value);
    }
  }
}

This RadioGroup component provides a robust, accessible, and highly customizable solution for radio button groups in Angular applications.