# Radio Group

Choose one option from a related set.

Notification frequency options

[Complete TSX file](/examples/radio-group/default.tsx)

```tsx
"use client";

import { Label } from "@/siteplane/components/label";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

export default function Example() {
  return (
    <RadioGroup aria-label="Notification frequency" defaultValue="daily">
      <Label>
        <Radio value="instant" />
        Instantly
      </Label>
      <Label>
        <Radio value="daily" />
        Daily digest
      </Label>
      <Label>
        <Radio value="weekly" />
        Weekly summary
      </Label>
    </RadioGroup>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/radio-group
```

With npm: `npx shadcn@4.18.0 add @siteplane/radio-group`. [Registry JSON](/registry/radio-group.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/fieldset @siteplane/form @siteplane/input @siteplane/label
```

Field is installed with Input.

## Usage

```tsx
import { Label } from "@/siteplane/components/label";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

<RadioGroup aria-label="Notification frequency" defaultValue="daily">
  <Label>
    <Radio value="daily" />
    Daily digest
  </Label>
  <Label>
    <Radio value="weekly" />
    Weekly summary
  </Label>
</RadioGroup>;
```

## Composition

```text
RadioGroup
├── Label + Radio
└── Label + Radio
```

## With description

Give each option a `FieldItem` with the radio beside a column of `FieldLabel` and `FieldDescription`, and render the `Field` as a [Fieldset](/docs/components/fieldset/) whose legend names the group. Each FieldItem connects its label and description to its radio, so the description is announced separately from the name.

Radio options with descriptions

[Complete TSX file](/examples/radio-group/with-description.tsx)

```tsx
"use client";

import {
  Field,
  FieldDescription,
  FieldItem,
  FieldLabel,
} from "@/siteplane/components/field";
import { Fieldset, FieldsetLegend } from "@/siteplane/components/fieldset";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

const options = [
  {
    description: "Only invited members can see this workspace.",
    label: "Private",
    value: "private",
  },
  {
    description: "Everyone in your organization can find and join it.",
    label: "Organization",
    value: "organization",
  },
];

export default function Example() {
  return (
    <Field name="visibility" render={(props) => <Fieldset {...props} />}>
      <FieldsetLegend>Workspace visibility</FieldsetLegend>
      <RadioGroup defaultValue="private">
        {options.map((option) => (
          <FieldItem className="items-start gap-2" key={option.value}>
            <Radio value={option.value} />
            <div className="grid gap-1">
              <FieldLabel>{option.label}</FieldLabel>
              <FieldDescription>{option.description}</FieldDescription>
            </div>
          </FieldItem>
        ))}
      </RadioGroup>
    </Field>
  );
}
```

## Controlled selection

Pass `value` and `onValueChange` to own the selected value. A disabled option cannot be selected.

Controlled radio group with a disabled option

[Complete TSX file](/examples/radio-group/controlled.tsx)

```tsx
"use client";

import { useState } from "react";
import { Label } from "@/siteplane/components/label";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

export default function Example() {
  const [value, setValue] = useState("editor");
  return (
    <RadioGroup
      aria-label="Default role for new members"
      onValueChange={setValue}
      value={value}
    >
      <Label>
        <Radio value="viewer" />
        Viewer
      </Label>
      <Label>
        <Radio value="editor" />
        Editor
      </Label>
      <Label>
        <Radio disabled value="admin" />
        Admin
      </Label>
    </RadioGroup>
  );
}
```

## Card style

Wrap each radio and its text in `Label variant="card"` so the whole card selects the option.

Plan cards

[Complete TSX file](/examples/radio-group/card-style.tsx)

```tsx
"use client";

import { Label } from "@/siteplane/components/label";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

const plans = [
  {
    description: "€9 per month for individuals and small projects.",
    title: "Starter",
    value: "starter",
  },
  {
    description: "€29 per month for growing teams.",
    title: "Growth",
    value: "growth",
  },
  {
    description: "Custom pricing with single sign-on and audit logs.",
    title: "Enterprise",
    value: "enterprise",
  },
];

export default function Example() {
  return (
    <RadioGroup
      aria-label="Plan"
      className="w-full max-w-sm"
      defaultValue="growth"
    >
      {plans.map((plan) => (
        <Label
          variant="card"
          className="flex w-full items-start"
          key={plan.value}
        >
          <Radio value={plan.value} />
          <span className="flex flex-col gap-1">
            <span>{plan.title}</span>
            <span className="text-muted-foreground text-xs">
              {plan.description}
            </span>
          </span>
        </Label>
      ))}
    </RadioGroup>
  );
}
```

## Form integration

Render the `Field` as a `Fieldset`: the legend names the group, and the form submits the selected value under the field’s `name`.

Radio group in a form

[Complete TSX file](/examples/radio-group/form.tsx)

```tsx
"use client";

import { Button } from "@/siteplane/components/button";
import { Field, FieldItem, FieldLabel } from "@/siteplane/components/field";
import { Fieldset, FieldsetLegend } from "@/siteplane/components/fieldset";
import { Form } from "@/siteplane/components/form";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

export default function Example() {
  return (
    <Form
      className="grid w-full max-w-sm gap-4"
      onSubmit={(event) => {
        event.preventDefault();
      }}
    >
      <Field name="billing" render={(props) => <Fieldset {...props} />}>
        <FieldsetLegend>Billing period</FieldsetLegend>
        <RadioGroup defaultValue="monthly">
          <FieldItem>
            <FieldLabel>
              <Radio value="monthly" />
              Monthly
            </FieldLabel>
          </FieldItem>
          <FieldItem>
            <FieldLabel>
              <Radio value="yearly" />
              Yearly
            </FieldLabel>
          </FieldItem>
        </RadioGroup>
      </Field>
      <Button type="submit">Save</Button>
    </Form>
  );
}
```

## Sizes

Set `size` on the group; each radio inherits it unless it sets its own.

Radio group sizes

[Complete TSX file](/examples/radio-group/sizes.tsx)

```tsx
"use client";

import { Label } from "@/siteplane/components/label";
import { Radio, RadioGroup } from "@/siteplane/components/radio-group";

const sizes = [
  ["sm", "small"],
  ["md", "medium"],
  ["lg", "large"],
  ["xl", "extra large"],
] as const;

export default function Example() {
  return (
    <div className="flex flex-wrap gap-8">
      {sizes.map(([size, name]) => (
        <RadioGroup
          aria-label={`Billing period, ${name}`}
          defaultValue="monthly"
          key={size}
          size={size}
        >
          <Label>
            <Radio value="monthly" />
            Monthly
          </Label>
          <Label>
            <Radio value="yearly" />
            Yearly
          </Label>
        </RadioGroup>
      ))}
    </div>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| RadioGroup.size | sm \| md \| lg \| xl | provider / lg | Shared item size. |
| value / defaultValue | unknown | uncontrolled | Controlled or initial selection. |
| onValueChange | (value, eventDetails) => void | unset | Selection change callback. |
| disabled | boolean | false | Disables the group. |
| Radio.size | sm \| md \| lg \| xl | group / provider / lg | Per-item dimensions. |
| Radio.value | unknown | required | Unique option value. |

The group places its options 8 px apart. A `gap-*` utility in `className` replaces that, so a horizontal group sets its own, for example `className="flex-row gap-4"`.

`RadioProps`, `RadioGroupProps`, `RadioPrimitive` and `RadioGroupPrimitive` are exported. `radioVariants({ className })` returns the utility classes Radio adds beside its stylesheet. The look and the size come from the kit stylesheet through the attributes Radio renders; the helper does not create radio behavior.

Other props, refs, validation and events follow [Base UI RadioGroup](https://base-ui.com/react/components/radio-group). Styled parts accept string className.

[Read the installed source](/source/radio-group).

## Usage notes

Tab enters the radio group; arrow keys move among enabled choices. Give both the group and each item an accessible label. Use CheckboxGroup for multiple independent values.

A selected radio fills with the brand accent, and its dot takes the text color for accent fills, `--sp-rw-fg-on-accent`, which a light brand sets dark ([Theming](/docs/customization/theming/#override-component-colors)).
