Radio
A set of options where people pick exactly one. For a few options shown as buttons, use TabButtons.
<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.
<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.
<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.
<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.
| Keys | Action |
|---|---|
Tab | Move focus into the group, to the selected option |
ArrowDown / ArrowUp | Select the next or previous option (vertical) |
ArrowRight / ArrowLeft | Select the next or previous option (horizontal) |
Space | Select 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
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')The value this option represents within its group.
Disables this option only. The group's `disabled` still wins when set.
Label rendered above (or beside, for binary controls) the input.
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.
HTML id of the underlying control. Auto-generated via `useId()` if omitted.
| Slot | Payload |
|---|---|
label | — Overrides the rendered label content. |
description | — Overrides the rendered description content. |
Overrides the rendered label content.
Overrides the rendered description content.
RadioGroup
Show types
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')The selected value of the group.
Size of every radio in the group.
Wraps each option in a clickable surface with hover, active and focus states — useful for selection lists and menu items.
Disables every radio in the group.
Layout of the options. Also decides which arrow keys move the selection: up/down when vertical, left/right when horizontal.
When `true`, arrow-key navigation wraps from the last option back to the first.
Native `name` for the hidden form input, so the group submits with a form. Auto-generated when omitted.
Label rendered above (or beside, for binary controls) the input.
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 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.
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.
HTML id of the underlying control. Auto-generated via `useId()` if omitted.
| Slot | Payload |
|---|---|
default | — The `<Radio>` options. |
label | { required: boolean; } Overrides the rendered group heading. Receives `{ required }`. |
description | — Overrides the rendered group description. |
The `<Radio>` options.
Overrides the rendered group heading. Receives `{ required }`.
Overrides the rendered group description.
| Event | Payload |
|---|---|
update:modelValue | [value: RadioValue | undefined] Fired when the model value changes. |
Fired when the model value changes.
| Template ref | Type |
|---|---|
focus | (options?: FocusOptions) => void Moves focus to the checked radio, or to the first enabled radio when nothing is checked. |
Moves focus to the checked radio, or to the first enabled radio when nothing is checked.