RadioGroup
An items-driven radio group built on Reka UI's RadioGroup primitive.
Usage
<script setup lang="ts">
const choice = ref('one')
</script>
<template>
<SRadioGroup
v-model="choice"
:items="[{ label: 'One', value: 'one' }, { label: 'Two', value: 'two' }]"
/>
</template>
Simple options
A plain string in items is shorthand for { label: value, value }, for
when the label and value are the same:
<SRadioGroup v-model="size" :items="['sm', 'md', 'lg']" />
Orientation
orientation switches the layout between a vertical stack (the default)
and a horizontal row - it's also forwarded to the underlying primitive, so
arrow-key navigation follows the same axis (left/right instead of up/down):
<SRadioGroup v-model="shipping" orientation="horizontal" :items="['standard', 'express', 'overnight']" />
Description
A description on an item renders as a second, muted line under its
label - the item top-aligns with the label's first line instead of
centering against the whole two-line block whenever it's present:
<SRadioGroup
v-model="plan"
:items="[
{ label: 'Free', value: 'free', description: 'Basic features, community support.' },
{ label: 'Pro', value: 'pro', description: 'Everything in Free, plus priority support.' },
]"
/>
For anything beyond plain text - a price, a badge, custom markup - replace
the whole label block with the label slot instead; see Slots.
Card style
variant="card" wraps each option in its own bordered box instead of a
plain inline row, highlighting whichever one is selected. Works the same
with or without description:
<SRadioGroup
v-model="plan"
variant="card"
:items="[
{ label: 'Free', value: 'free', description: 'Basic features, community support.' },
{ label: 'Pro', value: 'pro', description: 'Everything in Free, plus priority support.' },
]"
/>
Colors
color sets the checked-state ring/dot color (and, combined with
variant="card", the card's own highlight border/tint) - unchecked always
stays the same neutral ring regardless:
<SRadioGroup v-model="primary" color="primary" :items="['one', 'two']" />
<SRadioGroup v-model="success" color="success" :items="['one', 'two']" />
<SRadioGroup v-model="danger" color="danger" :items="['one', 'two']" />
States
Setting disabled on an individual item (rather than the whole
SRadioGroup) disables just that one, leaving the rest of the group
interactive - including when that item happens to already be the selected
one, e.g. a plan a consumer picked before it was discontinued:
<SRadioGroup
v-model="shipping"
:items="[
{ label: 'Standard', value: 'standard' },
{ label: 'Express', value: 'express', disabled: true },
{ label: 'Overnight', value: 'overnight' },
]"
/>
<SRadioGroup
v-model="plan"
:items="[
{ label: 'Legacy (discontinued)', value: 'legacy', disabled: true },
{ label: 'Starter', value: 'starter' },
{ label: 'Pro', value: 'pro' },
]"
/>
disabled on the group itself disables every item at once instead of just
one, and invalid switches every item's ring and card state to the semantic
danger role:
<SRadioGroup v-model="frozen" disabled :items="['one', 'two', 'three']" />
<SRadioGroup v-model="invalidChoice" invalid :items="['one', 'two', 'three']" />
See Props.
Sizes
size takes sm / md / lg, scaling the item, indicator, hover/focus
halo, and label text together:
<SRadioGroup v-model="size" size="sm" :items="['one', 'two']" />
<SRadioGroup v-model="size" size="md" :items="['one', 'two']" />
<SRadioGroup v-model="size" size="lg" :items="['one', 'two']" />
Forms integration
Wrap it in FormField to get id/name/invalid and
aria-describedby wired up automatically - invalid and size both fall
back to FormField's own state when not set directly on the radio group.
Accessibility
RadioGroup renders Reka UI's RadioGroup primitive, so the accessibility
semantics come from there rather than being reimplemented here: the group
itself is role="radiogroup" and each item is role="radio" with
aria-checked. The whole group is a single Tab stop (roving
tabindex, not one stop per item) - once focus is inside, the arrow keys move
focus between items and immediately select whichever one it lands on (up/
down for the default vertical layout, left/right when orientation is
horizontal), the standard behavior for the
ARIA radio group pattern,
not something to configure here.
Custom :ui
To see exactly what you'd be overriding - the current default classes for
every slot and variant - here's RadioGroup's own theme file:
Props
| Prop | Type | Default |
|---|---|---|
id | string | - |
name | string | - |
items | (string | { label: string; value: string; disabled?: boolean; description?: string })[] | - |
modelValue | string | - |
disabled | boolean | false |
invalid | boolean | false |
size | 'sm' | 'md' | 'lg' | md |
color | ColorRole (built-in or registered semantic role) | primary |
orientation | 'vertical' | 'horizontal' | vertical |
variant | 'default' | 'card' | default |
ui | Partial<Record<'root' | 'itemWrapper' | 'item' | 'indicator' | 'label' | 'description' | 'labelGroup', string | object>> | - |
Slots
| Slot | Props | Description |
|---|---|---|
label | { item, checked, disabled } | Replaces an item's whole label block (its label text and, if set, description) - useful for a price, badge, or other rich content (default: the item's label/description) |