Skip to main content

Radio

A headless radio button component that provides accessible, single-selection functionality. Radio buttons allow users to select exactly one option from a set of choices.

Features

  • Single-selection: Only one radio can be selected at a time within a group
  • Keyboard navigation: Arrow keys navigate between options with roving tabindex
  • Activation modes: Automatic (selection follows focus) or manual (requires Enter/Space)
  • Full accessibility: ARIA radiogroup pattern with proper roles and states
  • Form integration: Automatic hidden input generation for native form submission
  • Flexible rendering: Works with any element via as prop or renderless mode

Basic Usage

<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'

const selected = ref<string>()
</script>

<template>
  <Radio.Group v-model="selected">
    <label>
      <Radio.Root value="small">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Small
    </label>
    <label>
      <Radio.Root value="medium">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Medium
    </label>
    <label>
      <Radio.Root value="large">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Large
    </label>
  </Radio.Group>
</template>

Components

Radio.Group

Container for managing radio buttons with single-selection behavior.
modelValue
T
The currently selected value
name
string
Form field name shared by all radios. Enables native form submission
disabled
boolean
default:"false"
Disables all radio buttons in the group
mandatory
boolean | 'force'
default:"false"
  • false: Selection can be cleared (not typical for radios)
  • true: Selection cannot be cleared once set
  • 'force': Auto-selects first non-disabled item on mount
activation
'automatic' | 'manual'
default:"'automatic'"
  • 'automatic': Arrow keys change selection (ARIA standard)
  • 'manual': Arrow keys only move focus, Enter/Space to select
label
string
Accessible name for the radio group
as
string
default:"'div'"
HTML element to render as
renderless
boolean
default:"false"
Render only slot content without wrapper
namespace
string
default:"'v0:radio:group'"
Context namespace for child radios

Slot Props

isDisabled
boolean
Whether the group is disabled
isNoneSelected
boolean
Whether no radio is selected
activation
'automatic' | 'manual'
Current activation mode
attrs
object
Pre-computed ARIA attributes including role="radiogroup"

Radio.Root

Individual radio button component. Must be used within Radio.Group.
value
unknown
required
Value associated with this radio button
disabled
boolean | Ref<boolean>
default:"false"
Disables this specific radio button
label
string
Optional display label (passed to slot props)
id
string
Unique identifier (auto-generated if not provided)
form
string
Associate with a form element by ID
as
string
default:"'button'"
HTML element to render as
renderless
boolean
default:"false"
Render only slot content without wrapper
namespace
string
default:"'v0:radio:root'"
Context namespace for child components
groupNamespace
string
default:"'v0:radio:group'"
Namespace for connecting to parent group

Slot Props

isChecked
boolean
Whether this radio is selected
isDisabled
boolean
Whether this radio is disabled
select
() => void
Function to select this radio
attrs
object
Pre-computed ARIA and data attributes including:
  • role="radio"
  • aria-checked
  • tabindex (roving tabindex: 0 for selected/first, -1 for others)
  • data-state="checked" | "unchecked"

Radio.Indicator

Visual indicator that displays when the radio is selected.
as
string
default:"'span'"
HTML element to render as
renderless
boolean
default:"false"
Render only slot content without wrapper
namespace
string
default:"'v0:radio:root'"
Context namespace to inject from parent

Slot Props

isChecked
boolean
Whether the radio is selected
attrs
object
Contains data-state and visibility style
The indicator is automatically hidden via visibility: hidden when the radio is not selected.

Radio.HiddenInput

Hidden input for native form submission. Automatically rendered when name prop is provided to Radio.Group.
name
string
Form field name (inherited from group if not provided)
value
unknown
Value to submit (overrides context value)
form
string
Associate with form by ID (overrides context form)
namespace
string
default:"'v0:radio:root'"
Context namespace to inject from parent

Advanced Examples

Manual Activation Mode

<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'

const filter = ref('all')
</script>

<template>
  <Radio.Group v-model="filter" activation="manual">
    <label>
      <Radio.Root value="all">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      All items
    </label>
    <label>
      <Radio.Root value="active">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Active only
    </label>
    <label>
      <Radio.Root value="archived">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Archived only
    </label>
  </Radio.Group>
</template>
In manual mode, arrow keys only move focus. Users must press Enter or Space to actually change the selection. This is useful for toolbar radio groups where you want deliberate selection.

Mandatory Selection

<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'

const plan = ref<string>()
</script>

<template>
  <Radio.Group v-model="plan" mandatory="force">
    <label>
      <Radio.Root value="free">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Free Plan
    </label>
    <label>
      <Radio.Root value="pro">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Pro Plan ($9/mo)
    </label>
    <label>
      <Radio.Root value="enterprise">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Enterprise
    </label>
  </Radio.Group>
</template>

Renderless Mode

<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'

const theme = ref('light')
</script>

<template>
  <Radio.Group v-model="theme" renderless v-slot="{ attrs }">
    <div v-bind="attrs" class="theme-selector">
      <Radio.Root value="light" renderless v-slot="{ attrs, isChecked }">
        <button 
          v-bind="attrs"
          class="theme-option"
          :class="{ selected: isChecked }"
        >
          <Radio.Indicator renderless v-slot="{ isChecked }">
            <svg v-if="isChecked" class="checkmark">
              <circle cx="12" cy="12" r="6" />
            </svg>
          </Radio.Indicator>
          Light theme
        </button>
      </Radio.Root>

      <Radio.Root value="dark" renderless v-slot="{ attrs, isChecked }">
        <button 
          v-bind="attrs"
          class="theme-option"
          :class="{ selected: isChecked }"
        >
          <Radio.Indicator renderless v-slot="{ isChecked }">
            <svg v-if="isChecked" class="checkmark">
              <circle cx="12" cy="12" r="6" />
            </svg>
          </Radio.Indicator>
          Dark theme
        </button>
      </Radio.Root>
    </div>
  </Radio.Group>
</template>

Disabled Options

<script setup lang="ts">
import { Radio } from '@vuetify/v0'
import { ref } from 'vue'

const shipping = ref('standard')
const isPremiumUser = ref(false)
</script>

<template>
  <Radio.Group v-model="shipping" name="shipping_method">
    <label>
      <Radio.Root value="standard">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Standard (5-7 days) - Free
    </label>
    <label>
      <Radio.Root value="express">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Express (2-3 days) - $10
    </label>
    <label>
      <Radio.Root value="overnight" :disabled="!isPremiumUser">
        <Radio.Indicator></Radio.Indicator>
      </Radio.Root>
      Overnight - Premium members only
    </label>
  </Radio.Group>
</template>

Accessibility

ARIA Radiogroup Pattern

The Radio component implements the WAI-ARIA radiogroup pattern:
  • Uses role="radiogroup" on the group container
  • Uses role="radio" and aria-checked on each radio button
  • Implements roving tabindex for keyboard navigation
  • Only one radio is tabbable at a time (selected radio, or first if none selected)

Keyboard Navigation

  • Tab: Moves focus into/out of the radio group
  • Arrow keys: Navigate between radio buttons
    • In automatic mode: Selects the focused radio
    • In manual mode: Only moves focus
  • Space/Enter: Selects the focused radio (always works in both modes)

Screen Reader Support

  • Group announces as “radiogroup” with optional label
  • Each radio announces its label, checked state, and position
  • Disabled radios are announced as “disabled”

Type Safety

All components are fully typed with TypeScript generics:
import type { 
  RadioGroupProps,
  RadioRootProps,
  RadioIndicatorProps,
  RadioActivation, // 'automatic' | 'manual'
  RadioState // 'checked' | 'unchecked'
} from '@vuetify/v0'

Comparison with Other Components

ComponentSelectionUse Case
RadioSingle, mandatoryOne choice from mutually exclusive options
CheckboxMultiple, optionalMultiple independent choices
SwitchSingle booleanOn/off toggle state
SelectSingle or multipleLarge lists with search/filtering

Build docs developers (and LLMs) love