A radio group drawn as cards. Each card carries an optional icon, a title and a sentence saying what picking it means. The column count follows the number of options, so four cards sit two by two and never three plus an orphan.
When to reach for it#
Five or fewer choices that each need a sentence to explain: a run mode, a plan, a connection type. If the options are one word each, or there are more than five, use Select. If the choices switch panels of content, use Tabs.
SchemaForm renders the same component for x-widget: "mode-chooser"; this is that widget, exported.
Contract#
options:{ value, label?, description?, icon?, disabled? }[].labeldefaults tovalue.valueandonChange(value): controlled, one value or none.label: required, names the group for assistive tech.id,disabled,className.invalidandwarned: an error or warning edge around the group, with a little padding so the edge does not touch the cards.- Columns: 2 or 4 options use two columns from
sm, everything else uses three fromlg; one column on a phone. The count is not a prop. - Keyboard and roles: a
radiogroupofradiobuttons; each card is a tab stop and Space or Enter picks it.
Laws it enforces#
- Every action answers. Every enabled card shows the pointer, the picked card shows a primary border and ring, and a disabled card shows the refusal cursor.
- Fit, then truncate, never clip. Cards share a grid whose columns follow the count, so no card is left alone on a row.
Sanctioned combinations#
- Inside a form
Cardor aFormModal, above the fields the choice controls. - With
Field-style captions above it: put the caption in the surrounding layout and repeat it aslabelfor assistive tech.
Banned combinations#
- More than five options. The grid stops reading as a choice; use
Select. - Cards that only differ by a word. Without a sentence each, a
SelectorSegmentedcontrol is smaller and clearer. - Using it as navigation. Cards pick a value; they do not switch panels. Use
Tabs.
Before you adopt this#
- Does every option have a sentence that says what picking it changes?
- Are there five options or fewer?
- Is exactly one always the answer, or is "none yet" a valid state you show with an empty
value?
Travels with#
Card (a form block), FormModal, SchemaForm (the mode-chooser widget).
Snippet#
<OptionCards label="Run mode" value={mode} onChange={setMode} options={[ { value: "manual", label: "Manual", description: "You start every run." }, { value: "scheduled", label: "Scheduled", description: "Runs on a timer." }, { value: "push", label: "On push", description: "Runs when code lands." }, { value: "hybrid", label: "Hybrid", description: "Timer plus push." }, ]}/>