Skip to content
design
Components

Radio group

A visible set of mutually exclusive choices with descriptions, keyboard selection and form semantics.

On this page
Report format

Choose how much detail the reader needs.

Summary format selected.

Choose the report format with the pointer or arrow keys; unavailable options retain their explanation.
Code
import { RadioGroup } from "@bosunkit/components/radio-group";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const title: string = "Choose one report format";
export default function Example(): ReactElement {
  const [value,setValue]: [string,Dispatch<SetStateAction<string>>] = useState("summary");
  return <div><RadioGroup label="Report format" hint="Choose how much detail the reader needs." value={value} onValueChange={setValue}
    options={[
      {value:"summary",label:"Executive summary",description:"Decisions, outcomes and next priorities."},
      {value:"technical",label:"Technical report",description:"Architecture, implementation and verification evidence."},
      {value:"slides",label:"Presentation",description:"A narrative for a live discussion."},
      {value:"email",label:"Investor email",description:"Available after the recipient list is configured.",disabled:true},
    ]} /><p role="status">{value === "summary" ? "Summary format selected." : value === "technical" ? "Technical format selected." : "Presentation format selected."}</p></div>;
}
01One answerA radio group asks one question and accepts one choice. Give the group a visible name and each option a distinct label.
02Enough contextUse descriptions where the choices need explanation. A dot identifies the selected option in every theme.
03Predictable keysTab reaches the selected option; arrow keys move selection between enabled options. Choosing an option does not submit the form.

Choosing a presentation

Use a radio group when seeing the available options helps a decision. Use Select for a longer compact list, segmented control for short peer modes, and choice cards when each choice needs an illustration or more space.

The value / onValueChange pair is controlled; defaultValue starts an uncontrolled group. An empty group can remain unselected until the reader makes a choice. Do not invent a default for a consequential decision.

Layout and states

Layout density
Recorded density

This is a recorded preference.

Choose a density
Code
import { RadioGroup } from "@bosunkit/components/radio-group";
import type { ReactElement } from "react";
export const title: string = "Horizontal, read-only and invalid choices";
export default function Example(): ReactElement {
  const options: readonly {value:string;label:string}[] = [{value:"comfortable",label:"Comfortable"},{value:"compact",label:"Compact"}];
  return <div style={{display:"grid",gap:"var(--space-xl)"}}>
    <RadioGroup label="Layout density" options={options} orientation="horizontal" defaultValue="comfortable" />
    <RadioGroup label="Recorded density" options={options} value="compact" readOnly hint="This is a recorded preference." />
    <RadioGroup label="Choose a density" options={options} error="Choose a layout density before continuing." />
  </div>;
}

The vertical layout gives descriptions room. Horizontal groups wrap when the container becomes narrow. Disabled options stay visible and are skipped by keyboard selection. Read-only groups retain their value while preventing changes. Pass an error to mark and describe an invalid group; individual option descriptions remain associated with their own controls.

Form integration

Download format

Code
import { RadioGroup } from "@bosunkit/components/radio-group";
import { Button } from "@bosunkit/components/button";
import { useState, type Dispatch, type SubmitEvent, type ReactElement, type SetStateAction } from "react";
export const title: string = "Native submission and reset";
export default function Example(): ReactElement {
  const [result,setResult]: [string,Dispatch<SetStateAction<string>>] = useState("");
  function submit(event: SubmitEvent<HTMLFormElement>): void {
    event.preventDefault(); const data: FormData = new FormData(event.currentTarget);
    setResult(data.get("format") === "pdf" ? "Example form submitted PDF." : "Example form submitted Markdown.");
  }
  return <form onSubmit={submit} onReset={(): void => { setResult(""); }}>
    <RadioGroup label="Download format" name="format" required defaultValue="pdf" options={[{value:"pdf",label:"PDF"},{value:"markdown",label:"Markdown"}]} />
    <div style={{display:"flex",gap:"var(--space-sm)"}}><Button type="submit">Read form values</Button><Button type="reset" variant="ghost">Reset format</Button></div>
    <p role="status">{result}</p>
  </form>;
}

name submits the selected option's value. required uses native form validation; the caller supplies its own validation message when intercepting submission. Uncontrolled groups reset to their default when the owning form resets; controlled groups follow the caller's reset handler. form associates a group with a form elsewhere in the document. A disabled group contributes no value.

Save only when the caller's workflow requests it. The group does not fetch, persist or confirm an operation.

Do
Export format
Ask one question with distinct answers.
Don't
Include
Do not make independent inclusions mutually exclusive.

Props

PropTypeDefaultDescription
labelstring | undefined–Visible translated group name.
optionsreadonly RadioOption[] | undefined–Choices with unique values; exactly one can be selected.
childrenReactNode–Custom Radio.Root children; use options or children, not both.
hintstring | undefined–Supporting instructions announced with the group.
errorstring | undefined–Validation message; marks the group invalid.
valuestring | undefined–Controlled selected value.
defaultValuestring | undefined""Initial selection when uncontrolled.
onValueChangePrimitive.Props<string>["onValueChange"]–Reports a requested selection; the caller owns saving.
namestring | undefined–Submitted field name.
formstring | undefined–ID of an owning form outside the component's ancestors.
requiredboolean | undefined–Requires a choice before native form submission.
disabledboolean | undefined–Makes every option unavailable.
readOnlyboolean | undefined–Keeps the value focusable and submitted, but prevents changes.
orientation"vertical" | "horizontal" | undefined"vertical"Stacked by default; horizontal choices wrap when needed.
classNamestring | undefined–Additional group classes.
testIdstring | undefined–Root test ID.
refRef<HTMLDivElement> | undefined–Radio group element.
renderReact.ReactElement | ComponentRenderFn<RenderFunctionProps, State> | undefined–Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
styleReact.CSSProperties | ((state: State) => React.CSSProperties | undefined) | undefined–Style applied to the element, or a function that returns a style object based on the component's state.
inputRefReact.Ref<HTMLInputElement> | undefined–A ref to access the hidden input element.

Also takes every attribute of Omit<Primitive.Props<string>, "className">.