Radio ​

A set of options where people pick exactly one. For a few options shown as buttons, use TabButtons.

label
description
size
orientation
<RadioGroup
  v-model="value"
  label="Notify me by"
>
  <Radio value="email" label="Email" />
  <Radio value="sms" label="SMS" />
</RadioGroup>

Anatomy ​

RadioGroup holds the selected value and the group's label, description and error. Each Radio inside it sets the value it stands for.

vue
<RadioGroup v-model="plan" label="Choose a plan">
  <Radio value="free" label="Free" />
  <Radio value="pro" label="Pro" description="For growing teams." />
</RadioGroup>

Examples ​

Choose a plan ​

required on RadioGroup puts the asterisk on the group label.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { Radio, RadioGroup } from 'frappe-ui'

const plan = ref()
</script>

<template>
  <RadioGroup v-model="plan" label="Choose a plan" required class="w-96">
    <Radio value="free" label="Free" />
    <Radio value="pro" label="Pro" />
    <Radio value="enterprise" label="Enterprise" />
  </RadioGroup>
</template>

Notification settings ​

padded makes each option a row with a clickable surface. A description shows below the label, and one option is disabled.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { Radio, RadioGroup } from 'frappe-ui'

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

<template>
  <RadioGroup v-model="notify" padded class="w-96">
    <Radio value="all" label="All new emails" />
    <Radio
      value="important"
      label="Important emails only"
      description="Only emails where you're mentioned or assigned."
    />
    <Radio value="off" label="Mail notifications off" disabled />
  </RadioGroup>
</template>

Behavior ​

Inside a group ​

Radio must be inside a RadioGroup, and throws an error otherwise. The group handles arrow keys, focus and form submission, so each option needs only a value.

Value ​

The value can be a string, number or boolean. When nothing is selected, the value is undefined, which is where an unbound group starts.

Group props ​

required and error are set on RadioGroup only, because they describe the whole choice, not one option. size and padded are also set on the group and apply to every option. disabled on the group disables every option, and disabled on a Radio disables that option only.

Label, description and error ​

On RadioGroup, label renders above the options and description below them. error renders below the options and hides description. It takes a string, an array of strings (one line each), or an Error, the same values as ErrorMessage. An empty string or an empty array means no error. required adds a red asterisk to the group label.

The #label slot replaces the label text and the required marker, and receives { required }. A #description slot is not hidden by error. It renders above the error.

Orientation ​

orientation is vertical by default. horizontal lays the options in a row and switches the arrow keys to left and right. loop is true by default, so the arrow keys go from the last option back to the first.

Form submission ​

The group submits its value with a form. name sets the field name. Without it, a name is generated.

Attributes ​

class and style go to the layout wrapper. Everything else (name, aria-*, data-* and listeners) goes once to the interactive element, which for RadioGroup is the radio group itself.

Accessibility ​

RadioGroup renders an element with role="radiogroup", labelled by the group label. Each Radio renders a role="radio" button that covers the label and description.

KeysAction
TabMove focus into the group, to the selected option
ArrowDown / ArrowUpSelect the next or previous option (vertical)
ArrowRight / ArrowLeftSelect the next or previous option (horizontal)
SpaceSelect the focused option

While error is set, the group gets aria-invalid and an aria-errormessage that points to the error text.

API Reference ​

Radio ​

Show types
typescript
import type { ComputedRef, InjectionKey } from 'vue'
import type { ToggleSize } from '../../composables/inputTypes'
import type { InputLabelingProps } from '../../composables/useInputLabeling'

/** The value a radio represents within its group. */
export type RadioValue = string | number | boolean

export interface RadioGroupProps extends InputLabelingProps {
  /** The selected value of the group. */
  modelValue?: RadioValue

  /** Size of every radio in the group. */
  size?: ToggleSize

  /** Wraps each option in a clickable surface with hover, active and focus states — useful for selection lists and menu items. */
  padded?: boolean

  /** Disables every radio in the group. */
  disabled?: boolean

  /** Layout of the options. Also decides which arrow keys move the selection: up/down when vertical, left/right when horizontal. */
  orientation?: 'vertical' | 'horizontal'

  /** When `true`, arrow-key navigation wraps from the last option back to the first. */
  loop?: boolean

  /** Native `name` for the hidden form input, so the group submits with a form. Auto-generated when omitted. */
  name?: string
}

export interface RadioGroupEmits {
  /**
   * Fired when the selected value changes. `undefined` while nothing is
   * selected, which is what an unbound group starts at.
   */
  'update:modelValue': [value: RadioValue | undefined]
}

// `required` and `error` are intentionally omitted: a radio is one option within
// a group, so the asterisk and the error message belong on the group, not on
// each option. Set them on `<RadioGroup>` instead.
//
// `size` and `padded` are likewise group-level — mixing sizes within one group
// has no design meaning, so they are inherited rather than per-option.
export interface RadioProps extends Omit<
  InputLabelingProps,
  'required' | 'error'
> {
  /** The value this option represents within its group. */
  value: RadioValue

  /** Disables this option only. The group's `disabled` still wins when set. */
  disabled?: boolean
}

/** Group-level state that each `Radio` reads instead of taking as its own prop. */
export interface RadioGroupContext {
  size: ComputedRef<ToggleSize>
  padded: ComputedRef<boolean>
  disabled: ComputedRef<boolean>
}

export const RadioGroupContextKey: InjectionKey<RadioGroupContext> =
  Symbol('RadioGroupContext')
value*
RadioValue

The value this option represents within its group.

disabled
= false
boolean

Disables this option only. The group's `disabled` still wins when set.

label
string

Label rendered above (or beside, for binary controls) the input.

description
string

Helper text rendered below the input. Hidden when `error` is set. A `#description` slot is not: it renders beside the error, and is referenced alongside it.

id
string

HTML id of the underlying control. Auto-generated via `useId()` if omitted.

label
—

Overrides the rendered label content.

description
—

Overrides the rendered description content.

RadioGroup ​

Show types
typescript
import type { ComputedRef, InjectionKey } from 'vue'
import type { ToggleSize } from '../../composables/inputTypes'
import type { InputLabelingProps } from '../../composables/useInputLabeling'

/** The value a radio represents within its group. */
export type RadioValue = string | number | boolean

export interface RadioGroupProps extends InputLabelingProps {
  /** The selected value of the group. */
  modelValue?: RadioValue

  /** Size of every radio in the group. */
  size?: ToggleSize

  /** Wraps each option in a clickable surface with hover, active and focus states — useful for selection lists and menu items. */
  padded?: boolean

  /** Disables every radio in the group. */
  disabled?: boolean

  /** Layout of the options. Also decides which arrow keys move the selection: up/down when vertical, left/right when horizontal. */
  orientation?: 'vertical' | 'horizontal'

  /** When `true`, arrow-key navigation wraps from the last option back to the first. */
  loop?: boolean

  /** Native `name` for the hidden form input, so the group submits with a form. Auto-generated when omitted. */
  name?: string
}

export interface RadioGroupEmits {
  /**
   * Fired when the selected value changes. `undefined` while nothing is
   * selected, which is what an unbound group starts at.
   */
  'update:modelValue': [value: RadioValue | undefined]
}

// `required` and `error` are intentionally omitted: a radio is one option within
// a group, so the asterisk and the error message belong on the group, not on
// each option. Set them on `<RadioGroup>` instead.
//
// `size` and `padded` are likewise group-level — mixing sizes within one group
// has no design meaning, so they are inherited rather than per-option.
export interface RadioProps extends Omit<
  InputLabelingProps,
  'required' | 'error'
> {
  /** The value this option represents within its group. */
  value: RadioValue

  /** Disables this option only. The group's `disabled` still wins when set. */
  disabled?: boolean
}

/** Group-level state that each `Radio` reads instead of taking as its own prop. */
export interface RadioGroupContext {
  size: ComputedRef<ToggleSize>
  padded: ComputedRef<boolean>
  disabled: ComputedRef<boolean>
}

export const RadioGroupContextKey: InjectionKey<RadioGroupContext> =
  Symbol('RadioGroupContext')
modelValue
RadioValue

The selected value of the group.

size
= "sm"
ToggleSize

Size of every radio in the group.

padded
= false
boolean

Wraps each option in a clickable surface with hover, active and focus states — useful for selection lists and menu items.

disabled
= false
boolean

Disables every radio in the group.

orientation
= "vertical"
"vertical" | "horizontal"

Layout of the options. Also decides which arrow keys move the selection: up/down when vertical, left/right when horizontal.

loop
= true
boolean

When `true`, arrow-key navigation wraps from the last option back to the first.

name
string

Native `name` for the hidden form input, so the group submits with a form. Auto-generated when omitted.

label
string

Label rendered above (or beside, for binary controls) the input.

description
string

Helper text rendered below the input. Hidden when `error` is set. A `#description` slot is not: it renders beside the error, and is referenced alongside it.

error
ErrorMessageValue

Error message rendered below the input. When set, the control receives `aria-invalid="true"` and `data-state="invalid"`. Takes a string, an array of strings, or an `Error` whose `messages` are rendered as stacked lines (with `Error.message` as the fallback). This is the same value `ErrorMessage.message` takes. An empty array and an empty string both mean no error.

required
boolean

Marks the field as required. Renders an asterisk next to the label, with `sr-only` text that announces it, and forwards `required` / `aria-required` to the underlying control where the control's role allows it. `data-required` is set either way.

id
string

HTML id of the underlying control. Auto-generated via `useId()` if omitted.

default
—

The `<Radio>` options.

label
{ required: boolean; }

Overrides the rendered group heading. Receives `{ required }`.

description
—

Overrides the rendered group description.

update:modelValue
[value: RadioValue | undefined]

Fired when the model value changes.

focus
(options?: FocusOptions) => void

Moves focus to the checked radio, or to the first enabled radio when nothing is checked.