Components

Radio Group

Source
Choose one value from a visible set of labeled options.
Install with the CLI
npx @vyui/cli add radio-group

Overview

VyRadioGroup renders a themed single-choice field from strings, numbers, or item objects. It supports controlled and uncontrolled state, legends, descriptions, horizontal or vertical layout, semantic colors, four sizes, disabled items, required styling, and custom text slots.

Usage

<script setup lang="ts">
import { ref } from 'vue'
import { VyRadioGroup } from '@vyui/kit'

const plan = ref('pro')
const items = [
  { label: 'Free', value: 'free', description: 'For personal projects.' },
  { label: 'Pro', value: 'pro', description: 'For growing teams.' },
  { label: 'Enterprise', value: 'enterprise', description: 'For larger organizations.' },
]
</script>

<template>
  <VyRadioGroup
    v-model="plan"
    legend="Choose a plan"
    :items="items"
  />
</template>

For valid items, the value is a string or number. Each selection replaces the previous value.

Primitive items

String and number entries are normalized into matching labels and values.

<script setup lang="ts">
import { ref } from 'vue'
import { VyRadioGroup } from '@vyui/kit'

const seats = ref(5)
</script>

<template>
  <VyRadioGroup
    v-model="seats"
    legend="Team size"
    :items="[1, 5, 10, 25]"
    orientation="horizontal"
  />
</template>

Primitive numbers remain numbers in the emitted value; their visible labels use String(item).

Item descriptions and disabled choices

Object items can include supporting text and an item-level disabled state.

<script setup lang="ts">
import { ref } from 'vue'
import { VyRadioGroup } from '@vyui/kit'

const delivery = ref('standard')
const options = [
  {
    label: 'Standard',
    value: 'standard',
    description: 'Arrives in 3–5 business days.',
  },
  {
    label: 'Express',
    value: 'express',
    description: 'Arrives the next business day.',
  },
  {
    label: 'Same day',
    value: 'same-day',
    description: 'Unavailable for this address.',
    disabled: true,
  },
]
</script>

<template>
  <VyRadioGroup
    v-model="delivery"
    legend="Delivery speed"
    :items="options"
  />
</template>

Group-level disabled disables every choice; an item's disabled field affects only that radio.

Uncontrolled initial value

Use defaultValue when the parent does not need to keep the current selection reactive.

<VyRadioGroup
  default-value="comfortable"
  legend="Interface density"
  :items="[
    { label: 'Comfortable', value: 'comfortable' },
    { label: 'Compact', value: 'compact' }
  ]"
/>

For source-accurate selected-color styling, prefer v-model: the kit theme's checked variant is calculated from modelValue, while the core primitive owns uncontrolled state initialized by defaultValue.

Horizontal layout

Set orientation="horizontal" to wrap choices across a row.

<VyRadioGroup
  v-model="billingCycle"
  legend="Billing cycle"
  orientation="horizontal"
  color="secondary"
  size="lg"
  :items="[
    { label: 'Monthly', value: 'monthly' },
    { label: 'Yearly', value: 'yearly' }
  ]"
/>

Horizontal orientation changes layout only. It does not change item order or add keyboard navigation in the kit wrapper.

Custom legend, labels, and descriptions

The named slots replace built-in text. Item slots receive each normalized item and the controlled modelValue.

<VyRadioGroup v-model="account" :items="accounts">
  <template #legend>
    Pay with · Secure
  </template>

  <template #label="{ item, modelValue }">
    <text class="font-medium text-neutral-900">
      {{ item.label }}
      <text v-if="item.value === modelValue"> (selected)</text>
    </text>
  </template>

  <template #description="{ item }">
    <text class="text-xs text-neutral-500">
      Ending in {{ item.lastFour }}
    </text>
  </template>
</VyRadioGroup>

Additional object fields are retained on the normalized item and are available to both item slots.

The legend slot replaces the contents of the component's themed text node, so keep it text-compatible. The label and description slots render inside the item copy wrapper and can provide richer Lynx markup.

Features and behavior

  • modelValue controls the selected string or number; defaultValue initializes uncontrolled core state.
  • String and number entries become { value: item, label: String(item) }.
  • Object items resolve value from item.value, falling back to item.label.
  • Object labels resolve from item.label, falling back to String(value).
  • Every normalized item receives an internal id in the form <group-id>:<value>.
  • Object items should provide at least value or label, and resolved values should be unique because they are also used as render keys.
  • Tapping an enabled item updates the root value and emits update:modelValue.
  • Selecting the active radio again keeps it selected; the group does not clear itself.
  • Group-level and item-level disabled states are combined for the radio control.
  • required forwards to the core controls and adds a visual asterisk to the built-in legend.
  • The kit wrapper forwards name, id, and orientation, but does not expose the core root's dir or loop props.

Radio group item

FieldTypeDefaultDescription
labelstringString form of the resolved valueVisible item label.
descriptionstringundefinedSupporting text below the label.
valuestring | numberlabelSelection value, internal id suffix, and render key.
disabledbooleanfalsePrevents selection of this item.
[key: string]anyAdditional application data retained and exposed to item slots.

The items array also accepts primitive strings and numbers.

Props

PropTypeDefaultDescription
modelValuestring | numberundefinedControlled selection used by v-model.
defaultValuestring | numberundefinedInitial value for uncontrolled core state.
itemsArray<RadioGroupItemShape | string | number>undefinedChoices to normalize and render.
legendstringundefinedOptional visual heading replaced by the legend slot.
disabledbooleanfalseDisables every radio in the group.
requiredbooleanfalseForwards required state and decorates the built-in legend with an asterisk.
namestringundefinedName forwarded to the core radio group.
idstringGenerated Vue idGroup id and prefix used to generate item ids.
colorColor'primary'Semantic checked-state color.
size'sm' | 'md' | 'lg' | 'xl''md'Radio, indicator, legend, label, and description dimensions.
orientation'horizontal' | 'vertical''vertical'Wrapping row or stacked column layout.
classanyundefinedClasses merged onto the radio group root.
uiPartial<Record<RadioGroupSlot, any>>undefinedPer-instance theme slot overrides.

Color defaults to primary, secondary, success, info, warning, error, or neutral, and supports consumer registry extensions.

Emits

EventPayloadDescription
update:modelValuestring | numberEmitted when an enabled choice becomes selected.

Slots

SlotPropsDescription
legendReplaces the visual legend text.
label{ item, modelValue }Replaces every built-in item label. item is normalized.
description{ item, modelValue }Replaces every built-in item description. item is normalized.

modelValue in the item slots is the controlled prop value. In uncontrolled usage it can remain undefined even after the core selection changes.

Styling and theming

Override globally through appConfig.ui.radioGroup or locally with ui.

UI slotPurpose
rootOuter relative container. The class prop is also merged here.
fieldsetFlex layout containing the legend and items.
legendBuilt-in legend text and required marker.
itemOne radio and its copy, aligned from the top.
baseInteractive circular radio surface and checked semantic fill.
indicatorInner white dot rendered for the checked item.
containerAlignment wrapper around the circular control.
wrapperLabel and description column.
labelBuilt-in item label.
descriptionBuilt-in supporting text.

Theme variants

VariantValuesDefaultEffect
colorRegistered semantic colors'primary'Colors the checked radio surface and border.
sizesm, md, lg, xl'md'Scales radios, indicators, text, line containers, and gaps.
orientationhorizontal, vertical'vertical'Uses a wrapping row or stacked column for the fieldset.
checkedtrue, falsePer controlled itemApplies the semantic checked fill and thicker border.
disabledtrue, falseGroup or item stateReduces control and label opacity.
requiredtrue, falseFrom requiredAppends a red asterisk to the built-in legend.

Unchecked radios use a white surface with a neutral 300 border. Checked radios use the selected semantic color at shade 500, a two-pixel matching border, and a white inner dot.

Accessibility

Each core radio is exposed as an accessibility element with radio semantics mapped to a native button trait. It announces checked or unchecked, and disabled radios receive the disabled trait.

The kit wrapper renders visible labels and descriptions beside each control, but it does not currently forward an accessibility-label to the radio or associate the Lynx text nodes as native labels. The generated item id is not paired with a web <label>. Treat accessible naming as a current limitation, test the result with VoiceOver and TalkBack, and use RadioGroupRoot, RadioGroupItem, and RadioGroupIndicator from @vyui/core when explicit per-item accessibility labels are required.

The root does not add a native group or fieldset role, and the visual legend is not automatically announced as the group's accessible name. The required asterisk is also visual; provide separate validation messaging and do not rely on it alone.

Platform notes

  • The component uses Lynx view and text nodes rather than DOM <fieldset>, <input type="radio">, and <label> elements.
  • Selection is handled by native tap events through the core radio primitives.
  • The indicator is conditionally mounted only for the checked item.
  • The default theme is light-mode oriented and omits DOM focus-ring and hover styles.
  • name, id, and required are forwarded to core primitives, but the kit group does not provide standalone native browser form submission.
  • Core direction and looping metadata exist in @vyui/core; the kit wrapper does not expose them and does not implement DOM arrow-key behavior.
  • Checkbox for choosing any number of independent options.
  • Select for one choice in a compact trigger and popup.
  • Toggle Group for toolbar-like single or multiple selection.
  • Rating for an ordered numeric score.