Radio
A group of round buttons where choosing one clears the others.
npm publish --tag latest # cennge-ui@0.4.0
- Tab enters a group on its chosen radio; the arrow keys move and choose, round from the last to the first.
- A click on a label or its description chooses that radio.
- Version bump is vertical and answers ↑ and ↓; Publish under is horizontal and answers ← and →.
Field marks
- Import
import { RadioGroup, Radio } from "cennge-ui";- Size
- 2.7 KB gzip for this import, its dependencies aside
- Pattern
- WAI-ARIA radio group (Radix RadioGroup)
- Keyboard
- Arrow up Arrow down Arrow left Arrow right Space
- State
- Controlled with
valueandonValueChangeonRadioGroup, or uncontrolled withdefaultValue - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Tab stops
- One per group: the chosen radio, or the first one before a choice
- Orientation
- Vertical by default;
"horizontal"wraps onto more lines when narrow - Tests
- Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: form
- Source
- src/components/radio
When to use#
Use radios when there are a few options, only one can be chosen, and people decide better with all of them in view: a visibility, a version bump, a plan. Each option can carry a line of description that stays in view, where a Select shows it only while its list is open.
Similar#
- Select keeps the options in a list until it is opened; better when there are many of them or space is short.
- ToggleGroup is a row of buttons for switching a view; it has no
nameand sends nothing with a form. - Checkbox allows any number of choices at once.
Descriptions and a disabled option#
description sits under a radio's label, inside the same <label>, so a click on it chooses the radio too. A disabled radio stays visible and the arrow keys skip it.
import { Radio, RadioGroup } from "cennge-ui";
export default function VisibilityPicker() {
return (
<RadioGroup label="Visibility" defaultValue="public">
<Radio value="public" description="Anyone on the internet can see this repository">
Public
</Radio>
<Radio value="private" description="Only you and the people you invite">
Private
</Radio>
<Radio value="internal" description="Needs an organization" isDisabled>
Internal
</Radio>
</RadioGroup>
);
}Horizontal#
orientation="horizontal" lays the radios out in a row that wraps when it runs out of room, and the left and right arrows move between them.
pnpm add cennge-uiimport { useState } from "react";
import { Radio, RadioGroup } from "cennge-ui";
const commands: Record<string, string> = {
pnpm: "pnpm add cennge-ui",
npm: "npm install cennge-ui",
yarn: "yarn add cennge-ui",
bun: "bun add cennge-ui",
};
export default function PackageManager() {
const [manager, setManager] = useState("pnpm");
return (
<div className="flex flex-col gap-4">
<RadioGroup label="Package manager" orientation="horizontal" value={manager} onValueChange={setManager}>
{Object.keys(commands).map((name) => (
<Radio key={name} value={name}>
{name}
</Radio>
))}
</RadioGroup>
<code className="w-fit rounded-medium bg-surface-3 px-3 py-2 font-code text-[13px]">{commands[manager]}</code>
</div>
);
}Required, in a form#
With a name, the chosen value goes into the form's data. Inside a Form, isRequired stops a submit with nothing chosen: the label and the circles turn red, the browser's message shows under the group, and focus moves to the first radio.
import { useState } from "react";
import { Button, Form, Radio, RadioGroup } from "cennge-ui";
export default function ChangesetForm() {
const [sent, setSent] = useState<string | null>(null);
return (
<Form
onSubmit={(event) => {
event.preventDefault();
setSent(String(new FormData(event.currentTarget).get("section")));
}}
className="flex flex-col gap-4"
>
<RadioGroup name="section" label="Changelog section" orientation="horizontal" isRequired>
<Radio value="added">Added</Radio>
<Radio value="changed">Changed</Radio>
<Radio value="fixed">Fixed</Radio>
</RadioGroup>
<Button type="submit" color="primary" className="self-start">
Add changeset
</Button>
<p aria-live="polite" className="text-sm text-muted">
{sent === null ? "Nothing submitted yet" : `Submitted section=${sent}`}
</p>
</Form>
);
}Accessibility#
The group is a radiogroup named by its label, with the description or error linked by aria-describedby and aria-invalid while it has an error. Each radio is named by its own <label>. Moving with the arrow keys chooses as it goes, as the WAI-ARIA pattern says; Space chooses the radio in focus, and Enter does nothing.
Props#
RadioGroup#
Radio#
Also takes every attribute of <button>, except value, color, which mean something else here.