Radio Group
A set of checkable buttons where no more than one can be checked at a time.
RadioGroup is an exclusive choice among 2+ options (payment method, tier, orientation). It is semantically different from a Select: use Radio when all the options are visible at once.
Each RadioGroupItem renders the round button and its label: pass the text as children (or as the label prop). The initial selection is set with checked on one item; the island then keeps a single item checked per group. With name, the group renders a hidden <input> that holds the value of the checked item, so the choice posts with the surrounding form.
Installation
$ bext ui add radio-group
Copy and paste the following into src/components/ui/radio-group.tsx.
This component is interactive. Make sure the shared island is loaded (see Islands). It reacts to data-ui="radio-group" hooks.
Usage
Anatomy
<RadioGroup name="…" ariaLabel="…"> // role="radiogroup" ├── <RadioGroupItem value="a">Label A</RadioGroupItem> ├── <RadioGroupItem value="b" checked>Label B</RadioGroupItem> └── <RadioGroupItem value="c">Label C</RadioGroupItem>
API Reference
RadioGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | — | Form field name. Renders a hidden input that the island keeps equal to the value of the checked item (nothing is posted while no item is checked). Also written to data-name. |
| ariaLabel | string | — | Accessible name of the group (aria-label). |
| ariaLabelledby | string | — | id of a visible element that names the group (aria-labelledby). |
| id | string | — | id of the group. |
| children | ReactNode | — | The RadioGroupItems. |
| className | string | — | Extra classes on the group (grid gap-2 by default). |
RadioGroupItem
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Written to data-value on the radio button. Also the accessible name when no label is given. |
| checked | boolean | false | Initial state. Set it on one item only. |
| disabled | boolean | false | Disables the item: it is skipped by the arrow keys and cannot be checked. |
| children | ReactNode | — | Visible label, rendered in a <label> linked to the button. |
| label | string | — | Label text when no children are passed. Also used as the aria-label of the button. |
| id | string | "radio-" + value | id of the button, referenced by the label. Set it when the same value appears twice on a page. |
Data Attributes
Attributes you can target in custom CSS to override the style for each state.
| Attribute | On | Values | Description |
|---|---|---|---|
| data-ui="radio-group" | RadioGroup | — | Scope of the exclusive selection: checking one item unchecks the others in the same group. |
| data-ui="radio" | item button | data-value="…" | Clicking checks the item. |
| aria-checked | item button | "true" | "false" | Current state, set on render and updated by the island. The most reliable hook for styling. |
| data-state | item button | "checked" | "unchecked" | Same state, set on render and updated by the island. |
| data-ui="radio-input" | hidden input | — | Rendered when name is set. Its value follows the checked item and it emits a change event. |
Keyboard
| Key | Action |
|---|---|
| Tab / Shift+Tab | Moves focus into the group, onto the checked item (the first one when none is checked), then out of it. |
| Arrow Down / Arrow Right | Moves to the next item and checks it. Wraps to the first. |
| Arrow Up / Arrow Left | Moves to the previous item and checks it. Wraps to the last. |
| Space | Checks the focused item. |
Accessibility
RadioGroup sets role="radiogroup". Name it with ariaLabel, or with ariaLabelledby pointing at a visible heading or label. Each RadioGroupItem renders a <button role="radio"> with aria-checked and an aria-label (the label prop, otherwise the value), plus a <label for> for the visible text. When you pass the visible text as children, also pass label if the value is not a readable name.
The island keeps a roving tabindex: the group is a single tab stop, and the arrow keys move between items and check the item they land on. Disabled items are skipped.
Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.
On This Page