Radio
Lets users pick a single option from a set. RadioGroup owns the selected value; each Radio inside it declares the value it represents.
<RadioGroup v-model="plan" label="Choose a plan">
<Radio value="free" label="Free" />
<Radio value="pro" label="Pro" />
</RadioGroup>Radio must be used inside a RadioGroup — it throws otherwise. The group handles arrow-key navigation, roving focus and form submission, so options only need a value.
Playground
<RadioGroup
v-model="value"
label="Notify me by"
>
<Radio value="email" label="Email" />
<Radio value="sms" label="SMS" />
</RadioGroup>Required
The asterisk belongs on the group heading, not on each option — a radio is one choice within a group, so marking every option would be wrong. Set required on RadioGroup and it renders on the heading.
<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>States
<script setup lang="ts">
import { ref } from 'vue'
import { Radio, RadioGroup } from 'frappe-ui'
// Each state needs its own group — within one group only a single option can
// be selected, which is the whole point of a radio.
const unselected = ref('')
const selected = ref('on')
const disabled = ref('')
const disabledSelected = ref('on')
</script>
<template>
<div class="flex flex-col gap-3 items-start">
<RadioGroup v-model="unselected">
<Radio value="on" label="Unselected" />
</RadioGroup>
<RadioGroup v-model="selected">
<Radio value="on" label="Selected" />
</RadioGroup>
<RadioGroup v-model="disabled" disabled>
<Radio value="on" label="Disabled" />
</RadioGroup>
<RadioGroup v-model="disabledSelected" disabled>
<Radio value="on" label="Disabled selected" />
</RadioGroup>
</div>
</template>Settings list
Use padded for mutually exclusive settings. A description stacks below the label, and the whole row is the click target.
<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>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. */
'update:modelValue': [value: RadioValue]
}
// `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.
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. */
'update:modelValue': [value: RadioValue]
}
// `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.
Error message rendered below the input. When set, the control receives `aria-invalid="true"` and `data-state="invalid"`. May be either a string or an `Error` object whose `messages?: string[]` is rendered as stacked lines (with `Error.message` as the fallback).
Marks the field as required. Renders an asterisk next to the label and forwards `required` / `aria-required` to the underlying control.
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.