Skip to content

I9kRadioGroup ​

Fieldset of mutually exclusive radio options, rendered as a stacked list or a card grid. Unlike I9kInput, I9kTextarea, and I9kSelect, it does not participate in the I9kField composable — it owns its own legend, hint, error, and ids entirely.

View as MarkdownView source
ts
import { I9kRadioGroup } from '@9klabs/design';

Examples ​

Default ​

Choose a service
vue
<I9kRadioGroup v-model="service" legend="Choose a service" :options="options" />

Card variant ​

Choose a service
vue
<I9kRadioGroup
  v-model="service"
  legend="Choose a service"
  variant="card"
  :options="options"
/>

Required with error ​

Choose a service
vue
<I9kRadioGroup
  v-model="service"
  legend="Choose a service"
  required
  error="Choose the service you need."
  :options="options"
/>

Watch out ​

  • Never wrap I9kRadioGroup in I9kField — it does not call useI9kField() and will not inherit label, hint, error, or size from it; pass those as its own props instead.
  • options must match the I9kRadioOption shape exactly: { label, value, description?, disabled? }.
  • The card variant lays out options in a two-column grid (one column under 640px) with the native input visually hidden; default renders a plain stacked or wrapped list.

API ​

Props ​

PropTypeDefaultRequired
modelValuestring—yes
optionsreadonly I9kRadioOption[]—yes
legendstring—yes
namestringundefinedno
hintstringundefinedno
errorstringundefinedno
requiredbooleanfalseno
disabledbooleanfalseno
size'sm' | 'md' | 'lg''md'no
variant'default' | 'card''default'no
orientation'horizontal' | 'vertical''vertical'no

Emits ​

EventPayload
update:modelValue[value: string]

Types ​

ts
export interface I9kRadioOption {
  label: string;
  value: string;
  description?: string;
  disabled?: boolean;
}

Agent prompt ​

Paste this into an agent to have it use the component correctly. It is self-contained.

text
Use I9kRadioGroup from @9klabs/design for a single choice among a small, fully visible set of options.

import { I9kRadioGroup } from '@9klabs/design';
import type { I9kRadioOption } from '@9klabs/design';

Props:
- modelValue: string (required) — the v-model target, matched against each option's value.
- options: readonly I9kRadioOption[] (required) — each is { label: string; value: string; description?: string; disabled?: boolean }.
- legend: string (required) — the fieldset's accessible name; always rendered, there is no slot override.
- name?: string — the radio input group name; defaults to an auto-generated id.
- hint?: string
- error?: string — a defined value renders the error message instead of the hint and marks the group invalid.
- required?: boolean (default false)
- disabled?: boolean (default false) — disables the whole group.
- size?: 'sm' | 'md' | 'lg' (default 'md')
- variant?: 'default' | 'card' (default 'default')
- orientation?: 'horizontal' | 'vertical' (default 'vertical')

Emits: update:modelValue with the selected option's value.

Slots: none.

IMPORTANT: I9kRadioGroup never calls useI9kField() — do NOT wrap it in I9kField expecting it to inherit label, hint, error, or size the way I9kInput/I9kTextarea/I9kSelect do. It renders its own <fieldset><legend> and its own hint/error paragraphs directly from its own props. Wrapping it in I9kField still renders I9kField's separate label/hint/error around the group, producing duplicated, disconnected markup — pass legend, hint, error, required, and size straight to I9kRadioGroup instead, with no I9kField involved.

Usage:
<I9kRadioGroup v-model="service" legend="Choose a service" :options="[{ label: 'Technical audit', value: 'audit' }, { label: 'Design system', value: 'design' }]" />

Made with love 💚, caffeine, and Claude Code. Built with @9klabs/design itself.