Skip to content

Radio

A group of mutually exclusive options, laid out so the user sees them all at once.

View source View as Markdown

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.

App.vue
<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 orientation to '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 Label its own id, or inside a Field they share the field’s label ID and all register as its label.must