RadioGroup
The RadioGroup component provides a way to present mutually exclusive options where users can select only one choice. It supports both vertical and horizontal layouts, disabled options, and comprehensive validation with proper fieldset/legend semantics for accessibility.
Basic Radio Groups
Single selection from mutually exclusive options
Current Selections:
Payment: None selected
Theme: light
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | required | Form field name (shared by all radios) |
label | string | required | Group label text (legend) |
value | string | '' | Selected value (bindable) |
options | Option[] | required | Array of radio options |
required | boolean | false | Whether selection is required |
error | string \| null | null | Error message to display |
helper | string | - | Helper text below group |
orientation | 'vertical' \| 'horizontal' | 'vertical' | Layout direction |
Option Interface
interface Option {
value: string; // The value when selected
label: string; // Display text for the option
disabled?: boolean; // Whether option is selectable
} Usage Examples
Basic Radio Group
<script>
import RadioGroup from '$lib/shared/components/forms/RadioGroup.svelte';
let theme = $state('light');
const themeOptions = [
{ value: 'light', label: 'Light Theme' },
{ value: 'dark', label: 'Dark Theme' },
{ value: 'auto', label: 'Auto (System)' }
];
</script>
<RadioGroup
name="theme"
label="Theme Preference"
bind:value={theme}
options={themeOptions}
helper="Choose your preferred color scheme"
/> Horizontal Layout
<script>
import RadioGroup from '$lib/shared/components/forms/RadioGroup.svelte';
let priority = $state('medium');
const priorities = [
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' }
];
</script>
<RadioGroup
name="priority"
label="Task Priority"
bind:value={priority}
options={priorities}
orientation="horizontal"
helper="Select priority level"
/> With Disabled Options
<script>
import RadioGroup from '$lib/shared/components/forms/RadioGroup.svelte';
let plan = $state('');
const plans = [
{ value: 'free', label: 'Free Plan' },
{ value: 'pro', label: 'Pro Plan' },
{ value: 'enterprise', label: 'Enterprise Plan', disabled: true }
];
</script>
<RadioGroup
name="subscription"
label="Subscription Plan"
bind:value={plan}
options={plans}
helper="Enterprise plan requires sales consultation"
/> With Validation
<script>
import RadioGroup from '$lib/shared/components/forms/RadioGroup.svelte';
let selection = $state('');
let error = $state(null);
const options = [
{ value: 'yes', label: 'Yes, I agree' },
{ value: 'no', label: 'No, I disagree' }
];
function validate() {
if (!selection) {
error = 'Please make a selection';
} else {
error = null;
}
}
</script>
<RadioGroup
name="agreement"
label="Do you agree?"
bind:value={selection}
{options}
{error}
required
onchange={validate}
/> Semantic Structure
Fieldset and Legend
RadioGroup uses proper semantic HTML structure:
<fieldset role="radiogroup" aria-required="true">
<legend class="field-label required">Payment Method</legend>
<div class="radio-options">
<label>
<input type="radio" name="payment" value="credit" />
<span>Credit Card</span>
</label>
<!-- More options... -->
</div>
</fieldset> This structure provides:
- Grouping context - Screen readers understand options belong together
- Accessible labeling - Legend labels the entire group
- Navigation efficiency - Arrow keys move between options
- Form semantics - Proper form association
Typography System
The RadioGroup component uses design system typography tokens:
Applied Tokens
/* Legend/Group Label */
--typography-caption-size
--typography-caption-color
--typography-caption-weight
/* Option Labels */
--typography-body-size
--typography-body-color
--typography-body-weight
/* Helper/Error Text */
--typography-caption-size
--typography-muted-color /* helper */
--error /* error state */ Layout Options
Vertical Layout (Default)
- Space efficient - Stacks options vertically
- Easy scanning - Natural reading flow
- Mobile friendly - Works well on narrow screens
- Accessible - Clear association between options
Horizontal Layout
- Compact - Saves vertical space
- Quick comparison - Options side-by-side
- Responsive - Wraps on smaller screens
- Visual grouping - Shows options as a set
Styling
Visual Design
- Custom radio buttons - Styled to match design system
- Consistent spacing - Uses spacing tokens
- Focus indicators - Clear focus rings
- Disabled states - Reduced opacity
- Error styling - Red accents for invalid state
CSS Classes
.form-group- Container fieldset.field-label- Legend styling (with.requiredmodifier).field-choice-label- Individual option container.field-radio- Radio input styling.option-text- Option label text.field-error-message- Error message display.field-hint- Helper text styling
Layout Classes
/* Vertical layout */
.space-y-3 > * + * {
margin-top: var(--space-3);
}
/* Horizontal layout */
.flex.gap-6 {
display: flex;
gap: var(--space-6);
flex-wrap: wrap;
} Accessibility
ARIA Support
role="radiogroup"- Identifies the grouparia-required- Indicates required groupsaria-invalid- Communicates error statesaria-describedby- Links helper and error textrole="alert"- Error messages announced
Keyboard Interaction
- Tab - Navigate to radio group
- Arrow keys - Move between options within group
- Space - Select focused option
- Tab - Leave group (continues to next form field)
Screen Reader Support
- Legend announced when entering group
- Current selection status communicated
- Error messages read via live regions
- Option count and position announced
Validation Patterns
Required Selection
<script>
let value = $state('');
let error = $state(null);
function handleSubmit() {
if (!value) {
error = 'Please make a selection';
return false;
}
return true;
}
</script>
<RadioGroup
name="required-field"
label="Required Choice"
bind:value
{options}
{error}
required
/> Conditional Validation
<script>
let paymentMethod = $state('');
let cardType = $state('');
let cardError = $state(null);
$effect(() => {
if (paymentMethod === 'card' && !cardType) {
cardError = 'Please select card type';
} else {
cardError = null;
}
});
</script> Best Practices
When to Use Radio Groups
Use radio buttons when:
- Mutually exclusive choices - Only one option can be selected
- Visible options - All choices should be visible
- Small sets - 2-7 options work best
- Quick selection - Users need to compare options
Content Guidelines
- Clear labels - Each option should be descriptive
- Parallel structure - Use consistent phrasing
- Logical order - Arrange by frequency or importance
- Avoid negatives - Prefer positive phrasing
Layout Decisions
- Vertical for content - When options have different lengths
- Horizontal for UI - For short, similar-length options
- Group related options - Use visual grouping or spacing
- Consider mobile - Test touch targets and readability
Error Handling
- Clear messaging - Explain what selection is needed
- Contextual errors - Show errors near the group
- Preserve selection - Don’t reset on validation errors
- Recovery guidance - Help users fix the issue
Common Use Cases
Settings and Preferences
- Theme selection (light/dark/auto)
- Notification frequency
- Privacy levels
- Language selection
Forms and Surveys
- Multiple choice questions
- Rating scales
- Yes/No/Maybe responses
- Demographic information
E-commerce
- Shipping methods
- Payment options
- Product variants (size, color)
- Subscription plans
Configuration
- Feature toggles
- Permission levels
- View modes
- Sort orders
RadioGroup vs Other Components
vs Checkbox
- Radio: Single selection, mutually exclusive
- Checkbox: Multiple selections, independent choices
vs Select Dropdown
- Radio: All options visible, quick comparison
- Select: Space-efficient, many options supported
vs Toggle
- Radio: Multiple options, permanent choice
- Toggle: Binary choice, immediate effect