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.
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-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then 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.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/radio-groupIts 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.13import { 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
CheckboxGroupinstead when several choices may be picked,Selectfor a long list, andSwitchfor 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" verticalstacks the choices;horizontalputs 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
Radioelements, for a layoutoptionscannot 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 withoptions: 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 useserror:<RadioGroup error="Required.">.helperText: Not a prop. Ouro UI usesdescription:<RadioGroup description="...">.invalid: Not a prop.errormarks the group invalid:<RadioGroup error="What is wrong">.isDisabled: Not a prop. Ouro UI usesdisabled:<RadioGroup disabled>.isInvalid: Not a prop.errormarks the group invalid:<RadioGroup error="What is wrong">.isRequired: Not a prop. Ouro UI usesrequired:<RadioGroup required>.items: Not a prop. Ouro UI usesoptions:options={[{ value: "small", label: "Small" }]}.label: Not a prop. A group is named by itslegend:<RadioGroup legend="Size">.onChange: Not a prop. Ouro UI usesonValueChange, 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[];