Skip to content

RadioGroup

An items-driven radio group built on Reka UI's RadioGroup primitive.

Usage

vue
<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:

vue-html
<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):

vue-html
<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:

vue-html
<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:

vue-html
<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:

vue-html
<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:

vue-html
<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:

vue-html
<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:

vue-html
<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:

Theme source "radio-group" not found

Props

PropTypeDefault
idstring-
namestring-
items(string | { label: string; value: string; disabled?: boolean; description?: string })[]-
modelValuestring-
disabledbooleanfalse
invalidbooleanfalse
size'sm' | 'md' | 'lg'md
colorColorRole (built-in or registered semantic role)primary
orientation'vertical' | 'horizontal'vertical
variant'default' | 'card'default
uiPartial<Record<'root' | 'itemWrapper' | 'item' | 'indicator' | 'label' | 'description' | 'labelGroup', string | object>>-

Slots

SlotPropsDescription
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)
Edit this page