Skip to content
View as Markdown

Inputs

Radio

A single choice from a small set of visible, mutually exclusive options.

Import

TSX
import { Field, Radio, RadioGroup } from "@loamui/core";

Usage

Every example has a CSS tab. That’s the real, complete stylesheet for the component: plain, static CSS, with nothing running in the browser.

Basic group

A RadioGroup.Root shares one name so only one option can be selected; RadioGroup.Legend names the set.

Theme

With descriptions

Each option can carry helper text under its label.

Delivery

Arrives in 3-5 business days.

Guaranteed next-day delivery.

Horizontal

Lay the options out in a row only when there are two, short options. More than that, or longer labels, read better stacked.

Contact preference

Disabled option

Disable a single Radio to keep an unavailable option visible in the set. The rest of the group stays selectable.

Plan

Group description and error

RadioGroup.Description and RadioGroup.Error are joined to the group with aria-describedby. Set invalid on Root for validation: the message and the danger rings sit on the fieldset and its radios, and every choice stays selectable.

Plan

You can change it later.

In another language

The group's own words, the optional marker after the legend and the hidden prefix before an error, come from labels on the Root.

Formule (facultatif)

Guidance

When to use it

  • For choosing exactly one option from a small, visible set (roughly 2 to 5).
  • Always inside a RadioGroup.Root, which shares a name and labels the set with a <fieldset>/<legend>.

When not to

  • For many options: a Select is more compact.
  • For selecting several options: use Checkbox.
  • For a single on/off: use Checkbox or Switch.

Accessibility

  • RadioGroup.Root renders a native <fieldset> with a <legend>, the accessible way to name a group: screen readers announce the legend when a radio is focused.
  • Radios share one name so the browser enforces single-selection and arrow-key navigation natively.
  • RadioGroup.Root invalid sets aria-invalid on the fieldset; description and error IDs register after hydration, which carries role="radiogroup", the one place ARIA allows aria-invalid for radios. Required native groups take the same state after a submit attempt and clear it after a selection. The individual radios never claim it; their danger rings are pure CSS answering the group state.
  • Disabled is detected on the native input (:has(input:disabled) on the row), never declared on a wrapper.

How it works

A native radio, styled by accent-color

This is a plain <input type="radio">: no custom dot. The elements layer paints it with the platform's own accent-color (the neutral primary); selection, keyboard arrow-cycling and forced-colours support come from the browser. Field.Item supplies label and description associations; RadioGroup shares the native name and selection state.

Never pre-select

A group with a defaultValue lets users miss the question entirely and submit an answer they never gave, and once any radio is selected, the group can never be returned to unanswered. So when every option might be wrong, offer an explicit 'None of the above' option rather than leaving the user stuck. Omit defaultValue so the first selection is always a deliberate choice; reserve a default for the rare setting with one safe, overwhelmingly common value.

Order the options

List options alphabetically by default, so the order carries no editorial weight. Ordering by expected popularity needs extreme caution: it nudges users toward the top answers and, repeated across every form, can entrench the very distribution it assumed. Orders with intrinsic domain meaning (size, severity, date) are fine.

Controls sit left of labels

Compose the radio before its label text, keeping every control on the reading edge where screen-magnifier users panning a zoomed viewport will find it next to the text they are reading. Don't restyle labels to the other side: a right-hand control drifts out of the magnified view entirely.

Error messages

Say what happened and how to fix it, in the words of the question itself. See the writing guidance on the Field page.

SituationMessage
A yes/no question is unansweredSelect yes if [the thing is true]
A choice is unansweredSelect [whatever the legend asks for]

Props

PropTypeDefaultDescription
label / descriptionReactNode—Optional content for a complete labelled row. Omit both when composing the control inside Field.Label.
wrapperPropsOmit<PartProps<"label">, "children" | "htmlFor">—Props for the row rendered with label or description. className, style and ref on the component target its native input.
...othersInputHTMLAttributes—Native input props except type and size, including ref. Field.Item scopes each option’s label and description.

Parts

RadioGroup.Root

The group: a Fieldset.Root with role="radiogroup" that shares a name and the selection with the <Radio> options inside it, at any depth. Native <fieldset> props and ref are forwarded.

PropTypeDefaultDescription
invalidbooleanfalseExplicit validation state, available in server HTML. Message content is independent; supply aria-describedby for server-rendered associations.
namestring—Shared name for all radios (auto-generated if omitted).
valuestring—Controlled selected value (pair with onChange).
defaultValuestring—Initial selected value for uncontrolled usage.
onChange(value: string) => void—Fires with the newly selected value.
orientation"vertical" | "horizontal""vertical"Layout direction of the options.
labels{ optional?: ReactNode; errorPrefix?: ReactNode }{ optional: "(optional)", errorPrefix: "Error: " }The group's own words, read by the Legend and the Error. Pass them in the page's language.

RadioGroup.Legend

The group's name: Fieldset.Legend, so optional marks the group optional in words. Native <legend> props and ref are forwarded.

PropTypeDefaultDescription
optionalbooleanfalseAppends labels.optional ("(optional)"); optional is marked in words, not with an asterisk.

RadioGroup.Description

Helper text under the legend, joined to the group with aria-describedby. Native <p> props and ref are forwarded.

RadioGroup.Error

The group's error, announced with role="alert". Validation state is supplied by Root; empty content renders nothing. Native <p> props and ref are forwarded.