Radio
A group of mutually exclusive options, laid out so the user sees them all at once.
Radio is a group of mutually exclusive options that takes part in forms, and
behaves as one control with one label. Useful when the set is small enough to
show in full; once the list needs scrolling, reach for
Select.
<template>
<Radio.Group
v-model="plan"
aria-label="Plan"
class="flex flex-col gap-3"
:options="{ name: 'plan' }"
>
<div
v-for="option in plans"
:key="option.value"
class="flex items-start gap-3"
>
<Radio.Root
:id="`plan-${option.value}`"
:class="box"
:disabled="option.disabled"
:value="option.value"
>
<Radio.Indicator :class="dot" />
</Radio.Root>
<Label
:class="label"
:data-disabled="option.disabled ? 'true' : undefined"
:for="`plan-${option.value}`"
>
{{ option.label }}
</Label>
</div>
</Radio.Group>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Label } from '@maas/mirror/vue/Label'
import { Radio } from '@maas/mirror/vue/Radio'
const box = [
'flex size-5 items-center justify-center border-2 border-surface',
'rounded-component-round text-accent-on-solid',
'outline-4 outline-transparent transition-all duration-100 ease-linear focus-visible:focus-ring',
'active:border-primary-subtle',
'data-[state=checked]:bg-accent-solid',
'data-[state=checked]:border-[transparent]',
'data-[state=checked]:active:bg-accent-solid-active',
'data-[disabled=true]:border-disabled-subtle',
'data-[disabled=true]:data-[state=checked]:bg-disabled-solid',
'data-[disabled=true]:text-disabled-on-solid',
].join(' ')
const dot = 'rounded-component-round block size-2 bg-current'
const label = [
'type-component-xs text-surface translate-y-[0.1rem] [--mirror-label-cursor:pointer]',
'data-[disabled=true]:text-disabled-solid',
].join(' ')
const plans = [
{ value: 'hobby', label: 'Hobby', disabled: false },
{ value: 'team', label: 'Team', disabled: false },
{ value: 'enterprise', label: 'Enterprise', disabled: true },
]
const plan = ref('team')
</script>
Usage guidelines
- The ARIA radio group pattern keeps the whole group as one tab stop, so focus and selection travel together and the arrow keys commit a choice as they go. If the user should be able to browse before choosing, reach for something else.should
- Both arrow pairs move through the group by default, which is what the ARIA pattern asks for. Set
orientationto'horizontal'or'vertical'only where one axis belongs to something else, a slider or a scrolling panel beside the group.must - Give every per-item
Labelits ownid, or inside aFieldthey share the field’s label ID and all register as its label.must