Files
suaveplan/packages/ui/react/react-ui/src/components/radio-group/radio-group.md
2026-05-27 23:47:53 +01:00

7.1 KiB

RadioGroup

(no submodules)

- radio-group.variants — `radioGroupVariants` and `ra…
  • radio-group.variants — radioGroupVariants and radioGroupItemVariants are the cva (class-variance-authority) configurations powering `<Radi…

  • radio-group.variants — radioGroupVariants and `ra…

  • radio-group.variants — radioGroupVariants and radioGroupItemVariants are the cva (class-variance-authority) configurations powering `<Radi…

  • radio-group.variants — radioGroupVariants and `ra…

  • radio-group.variants — radioGroupVariants and radioGroupItemVariants are the cva (class-variance-authority) configurations powering `<Radi…

  • radio-group.variants — radioGroupVariants and `ra…

  • radio-group.variants — radioGroupVariants and radioGroupItemVariants are the cva (class-variance-authority) configurations powering `<Radi…

  • radio-group.variants — radioGroupVariants and radioGroupItemVariants are the cva (class-variance-authority) configurations powering `<Radi…

Purpose

<RadioGroup> and <RadioGroupItem> are the chassis radio-group primitives built on @radix-ui/react-radio-group. Radix supplies the a11y semantics (role="radiogroup" on the container, role="radio" on each item, roving tabindex across items, arrow-key navigation, native Space activation, controlled value propagation); the chassis layers a vertical-stack flex/gap layout via cva and a circular indicator dot rendered with <CircleIcon fill-current> from lucide-react.

Per the shadcn convention, items always sit inside the group via <RadioGroupItem> — rendering an item without a group is unsupported. Both components forward refs to their underlying Radix elements so consumers can focus / measure / drive animations.

Features

  • Container renders a <div role="radiogroup"> with grid gap-2 so items stack vertically with a small uniform gap (NOT space-y-* per the shadcn skill rule).
  • Items render a <button role="radio"> with the circular border-primary outline.
  • Three sizes for items (sm, default, lg) scaling the radio circle.
  • Indicator dot rendered via <CircleIcon fill-current text-current> so the dot inherits the surrounding text colour and the chassis switches it without an extra theme prop.
  • Controlled and uncontrolled use via Radix value / defaultValue / onValueChange.
  • disabled per-item or per-group blocks pointer + keyboard input.
  • Refs forwarded on both <RadioGroup> and <RadioGroupItem>.
  • motion-reduce:transition-none collapses transitions for prefers-reduced-motion: reduce.
  • Theme-driven variant overrides via useThemedVariants("RadioGroup" | "RadioGroupItem").

Usage: Basic Example

import { RadioGroup, RadioGroupItem } from "@suaveplan/react-ui";

export function PlanPicker() {
    return (
        <RadioGroup defaultValue="pro">
            <div className="flex items-center gap-2">
                <RadioGroupItem value="free" id="plan-free" />
                <label htmlFor="plan-free">Free</label>
            </div>
            <div className="flex items-center gap-2">
                <RadioGroupItem value="pro" id="plan-pro" />
                <label htmlFor="plan-pro">Pro</label>
            </div>
            <div className="flex items-center gap-2">
                <RadioGroupItem value="team" id="plan-team" />
                <label htmlFor="plan-team">Team</label>
            </div>
        </RadioGroup>
    );
}

Usage: Advanced Example

import { RadioGroup, RadioGroupItem, FieldSet, FieldLegend } from "@suaveplan/react-ui";
import { useState } from "react";

interface DeliverySpeed {
    readonly id: string;
    readonly label: string;
    readonly description: string;
    readonly disabled?: boolean;
}

export function DeliveryPicker({ options }: { options: readonly DeliverySpeed[] }) {
    const [speed, setSpeed] = useState("standard");

    return (
        <FieldSet>
            <FieldLegend>Delivery speed</FieldLegend>
            <RadioGroup value={speed} onValueChange={setSpeed}>
                {options.map((opt) => (
                    <div key={opt.id} className="flex items-start gap-2">
                        <RadioGroupItem
                            value={opt.id}
                            id={`delivery-${opt.id}`}
                            disabled={opt.disabled}
                            className="mt-1"
                        />
                        <div>
                            <label htmlFor={`delivery-${opt.id}`}>{opt.label}</label>
                            <p className="text-sm text-muted-foreground">
                                {opt.description}
                            </p>
                        </div>
                    </div>
                ))}
            </RadioGroup>
        </FieldSet>
    );
}

API Reference

<RadioGroup>

forwardRef<ComponentRef<typeof RadioGroupPrimitive.Root>, RadioGroupProps> — renders the Radix Root.

RadioGroupProps

Inherits every prop from Radix RadioGroupPrimitive.Root except asChild. Common props:

Prop Type Default Description
value string uncontrolled Currently selected value.
defaultValue string none Initial value for uncontrolled use.
onValueChange (value: string) => void none Fires on value change.
disabled boolean false Disables every item.
name string none Native form-field name.
orientation "horizontal" | "vertical" "vertical" Arrow-key navigation axis.

<RadioGroupItem>

forwardRef<ComponentRef<typeof RadioGroupPrimitive.Item>, RadioGroupItemProps> — renders a Radix Item containing the indicator dot.

RadioGroupItemProps

Inherits every prop from Radix RadioGroupPrimitive.Item except asChild. Adds:

Prop Type Default Description
size "sm" | "default" | "lg" "default" Visual variant scaling the radio circle.
value string required Unique value within the surrounding group.

radioGroupVariants / radioGroupItemVariants

Both bare cva resolvers exported for external composition.

Implementation Notes

  • The container uses grid gap-2 rather than flex flex-col gap-2 because grid collapses cross-axis layout in a way that keeps the items column-aligned even when they have wildly different content lengths (label + description blocks).
  • Per the shadcn skill rule, the chassis NEVER uses space-y-* — grid gap-* flows the same in RTL.
  • The indicator dot uses fill-current text-current so it inherits the surrounding text colour automatically; theme overrides do not need to special-case the dot.
  • Refs target the Radix Root + Item. The Item ref attaches to the rendered <button> so consumers can .focus() programmatically.
  • Items that require a description/help text typically wrap the radio + content in <div className="flex items-start gap-2"> with mt-1 on the radio so the dot aligns with the first line of text.