Radio
A headless radio button component for single-selection groups with keyboard navigation and roving tabindex.
Usage
Radio buttons must be used within a Radio.Group. Use v-model on the group to bind the selected value:
Selected: none
<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'
const selected = ref<string>()
const options = [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' },
]
</script>
<template>
<Radio.Group v-model="selected" class="flex flex-col gap-2">
<label
v-for="option in options"
:key="option.value"
class="inline-flex items-center gap-2"
>
<Radio.Root
class="size-5 border rounded-full inline-flex items-center justify-center border-divider data-[state=checked]:border-primary"
:value="option.value"
>
<Radio.Indicator class="size-2.5 rounded-full bg-primary" />
</Radio.Root>
<span>{{ option.label }}</span>
</label>
</Radio.Group>
<p class="mt-4 text-sm text-on-surface-variant">
Selected: {{ selected || 'none' }}
</p>
</template>
Anatomy
<script setup lang="ts">
import { Radio } from '@vuetify/v0'
</script>
<template>
<Radio.Group>
<Radio.Root>
<Radio.Indicator />
</Radio.Root>
<Radio.Root>
<Radio.Indicator />
</Radio.Root>
</Radio.Group>
<!-- With form submission -->
<Radio.Group>
<Radio.Root>
<Radio.Indicator />
<Radio.HiddenInput />
</Radio.Root>
<Radio.Root>
<Radio.Indicator />
<Radio.HiddenInput />
</Radio.Root>
</Radio.Group>
</template>Auto-Select First Option
Radio groups are inherently mandatory—once a selection is made, it can only be changed, not cleared. Use mandatory="force" to automatically select the first non-disabled option on mount:
Selected plan:
<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'
const plan = ref<string>()
const plans = [
{ value: 'free', label: 'Free', description: 'Basic features' },
{ value: 'pro', label: 'Pro', description: 'Advanced features' },
{ value: 'enterprise', label: 'Enterprise', description: 'Custom solutions' },
]
</script>
<template>
<Radio.Group v-model="plan" class="flex flex-col gap-3" mandatory="force">
<label
v-for="item in plans"
:key="item.value"
class="flex items-start gap-3 p-3 border rounded-lg border-divider has-[:checked]:border-primary has-[:checked]:bg-primary/5 cursor-pointer"
>
<Radio.Root
class="size-5 mt-0.5 border rounded-full inline-flex items-center justify-center border-divider data-[state=checked]:border-primary"
:value="item.value"
>
<Radio.Indicator class="size-2.5 rounded-full bg-primary" />
</Radio.Root>
<div>
<div class="font-medium">{{ item.label }}</div>
<div class="text-sm text-on-surface-variant">{{ item.description }}</div>
</div>
</label>
</Radio.Group>
<p class="mt-4 text-sm text-on-surface-variant">
Selected plan: {{ plan }}
</p>
</template>
Accessibility
The Radio components handle all ARIA attributes automatically:
role="radiogroup"on the Grouprole="radio"on each Rootaria-checkedreflects checked statearia-disabledwhen radio is disabledaria-requiredfor form validation (set on Group)aria-labelfrom thelabelpropRoving
tabindex- only the selected radio (or first if none) is tabbableSpace key selects the focused radio
Arrow keys navigate between radios
For custom implementations, use renderless mode and bind the attrs slot prop to your element:
<template>
<Radio.Root v-slot="{ attrs }" renderless>
<div v-bind="attrs">
<!-- Custom radio visual -->
</div>
</Radio.Root>
</template>Keyboard Navigation
Arrow keys provide circular navigation within a radio group:
| Key | Action |
|---|---|
Space | Select focused radio |
ArrowUp / ArrowLeft | Move to previous radio |
ArrowDown / ArrowRight | Move to next radio |
Navigation automatically skips disabled items and wraps around.
Form Integration
Set the name prop on Radio.Group to enable form submission for all radios in the group:
<template>
<Radio.Group v-model="selected" name="size">
<Radio.Root value="small">
<Radio.Indicator />
Small
</Radio.Root>
<Radio.Root value="large">
<Radio.Indicator />
Large
</Radio.Root>
</Radio.Group>
</template>Each Radio.Root automatically renders a hidden native radio input with the shared name and its own value.
For custom form integration, use Radio.HiddenInput explicitly:
<template>
<Radio.Group>
<Radio.Root value="a">
<Radio.Indicator />
<Radio.HiddenInput name="custom" value="override" />
</Radio.Root>
</Radio.Group>
</template>Radio.Root
Props
namespace
stringNamespace for context provision to children (Indicator, HiddenInput)
Default: "v0:radio:root"
Slots
Radio.Group
Props
mandatory
boolean | "force"Auto-selects the first non-disabled item on mount. Radio groups are inherently mandatory (selection can only be changed, not cleared), so `mandatory="force"` is the only meaningful option.
Default: false
activation
ActivationActivation mode controlling when selection occurs: - `automatic` (default): Selection follows focus (arrow keys select) - `manual`: Selection requires explicit Enter/Space key press Per WAI-ARIA APG, radio groups should have selection follow focus by default. Use `manual` for toolbar radio groups or when deliberate selection is preferred.
Default: "automatic"
Events
update:model-value
[value: T]Slots
Radio.HiddenInput
Props
value
stringSubmitted value when checked (defaults to context value or 'on'). Use to override the Radio.Root value for form submission.