Forms

RadioGroup

A group of radios under one legend, on the Field contract, for picking exactly one choice: pass legend, and options (or Radio children). The group is one tab stop; the arrow keys move between radios and pick as they go. With name, the chosen value submits as name=value with its form. error and required apply to the whole group.

Size

Serves 2.

Serves 4.

Serves 6.

In Spoonjoy

Spoonjoy, a recipe app, is built with Ouro HX (how).

In its source: app/components/moderation/ReportOrBlockButton.tsx

Install

Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):

npx shadcn@latest init --base base --preset nova --yes --no-monorepo
npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13

Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):

npx shadcn@latest add ourostack/ouro-ui/radio-group#v1.2.13

Or, with the @ouro namespace set up:

npx shadcn@latest add @ouro/ouro-base @ouro/radio-group

Its tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):

npx shadcn@latest add ourostack/ouro-ui/radio-group#v1.2.13 ourostack/ouro-ui/radio-group-tests#v1.2.13
import { RadioGroup } from "@/components/ouro/radio-group/radio-group";

Paths use shadcn's default @/ alias; the CLI rewrites them to yours.

Example

<RadioGroup
  legend="Size"
  name="size"
  defaultValue="medium"
  options={[
    { value: "small", label: "Small", description: "Serves 2." },
    { value: "medium", label: "Medium", description: "Serves 4." },
  ]}
/>

Use instead when

  • Use CheckboxGroup instead when several choices may be picked, Select for a long list, and Switch for on or off.

Props

The props the modes share. Type: RadioGroupProps.

className
Type: string
Layout only: margins, width, grid placement (mt-4, col-span-2). Appearance classes warn in development; style radios in your theme.
defaultValue
Type: string
The value chosen at first, for an uncontrolled group. Unset, nothing is chosen.
description
Type: ReactNode
Supporting text under the legend, read after it through aria-describedby.
disabled
Type: boolean
Disables every radio and fades the group.
error
Type: ReadonlyErrorMessages
What is wrong, from your server action or client check, shown under the choices; every radio gets aria-invalid. A message is text or a React node (one with a link); an empty string or array means valid.
hideLegend
Type: boolean
Hides the legend visually; screen readers still read it as the group's name.
legend (required)
Type: string
What is being chosen, shown above the choices and read as the group's name. Required: a group of controls needs a name. To hide it visually, keep it and add hideLegend.
name
Type: string
The form field name the chosen value submits under.
onValueChange
Type: ((value: string) => void)
Called with the chosen value when another radio is picked.
orientation
Type: "vertical" | "horizontal"
Default: "vertical"
vertical stacks the choices; horizontal puts short ones in a row that wraps.
ref
Type: Ref<HTMLDivElement>
The group element, a <div role="radiogroup">.
required
Type: boolean
A choice must be picked to submit the form: the group gets aria-required, and the legend a marker.
value
Type: string
The chosen value, for a controlled group; pair it with onValueChange.

Modes

RadioGroupProps is a union: pass the props of exactly one mode. Mixing them is a type error.

With children

children (required)
Type: ReactNode
The choices as Radio elements, for a layout options cannot express.

Not in this mode (typed never):

  • options: Not with children: pass the choices one way.

With options

options (required)
Type: RadioGroupOption[]
The choices, one radio each, in order.

Not in this mode (typed never):

  • children: Not with options: pass the choices one way.

Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

  • errorMessage: Not a prop. Ouro UI uses error: <RadioGroup error="Required.">.
  • helperText: Not a prop. Ouro UI uses description: <RadioGroup description="...">.
  • invalid: Not a prop. error marks the group invalid: <RadioGroup error="What is wrong">.
  • isDisabled: Not a prop. Ouro UI uses disabled: <RadioGroup disabled>.
  • isInvalid: Not a prop. error marks the group invalid: <RadioGroup error="What is wrong">.
  • isRequired: Not a prop. Ouro UI uses required: <RadioGroup required>.
  • items: Not a prop. Ouro UI uses options: options={[{ value: "small", label: "Small" }]}.
  • label: Not a prop. A group is named by its legend: <RadioGroup legend="Size">.
  • onChange: Not a prop. Ouro UI uses onValueChange, which gets the chosen value: onValueChange={setSize}.

className is for layout only

Pass margins, width and grid placement (mt-4, w-full, col-span-2).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.

Alignment classes (text-center) warn too: set alignment on a parent element.

Types

ErrorMessage

One error message: text, or a React element for a message that carries a link or other markup.

type ErrorMessage = string | ReactElement;

RadioGroupOption

One choice in a RadioGroup's options.

interface RadioGroupOption {
  /** What the group submits when this choice is picked, and the group's `value` then. */
  value: string;
  /** The choice's name, shown to the right of its radio. */
  label: string;
  /** Supporting text under the label, read after it. */
  description?: ReactNode | undefined;
  /** Disables this one radio; the arrow keys skip it. */
  disabled?: boolean | undefined;
}

ReadonlyErrorMessages

A read-only message view accepted by fields and Form; mutable input arrays remain valid.

type ReadonlyErrorMessages = ErrorMessage | readonly ErrorMessage[];