Skip to content

RadioGroup

SM · 950B gzip budget

A fieldset/legend group of real radio inputs.

Category
Forms & input
Budget
sm, single-purpose control
External runtime
React / React DOM only
axe fixtureSSR rendersource scanbundle budget

Preview

Live fixture
Preview locale
Choose one

Try the component here. Locale-aware formatting and direction follow the selected language; example content and labels are not automatically translated. Overlay demos use interactive launchers.

Install

npx shadcn@latest add https://gear5-ui.vercel.app/r/radio-group.json

Copies the source into your project. Pulls in 1 shared registry item: cn.

New here? Set up Tailwind and import aliases first →

Source & props

Edit on GitHub ↗

Prop types and inline documentation are included below. This is repository source; the installer rewrites shared imports for your project.

"use client";

import { useId } from "react";
import { cn } from "../lib/cn";

export interface RadioOption {
  value: string;
  label: string;
}

export interface RadioGroupProps {
  name: string;
  legend: string;
  options: RadioOption[];
  value: string;
  onChange: (value: string) => void;
  className?: string;
}

/**
 * A `<fieldset>`/`<legend>` pair around real radio inputs. The legend is the
 * group's name read once by a screen reader on entry — the piece a `<div>`
 * of styled buttons with `role="radio"` sprinkled on has to reimplement by
 * hand and usually gets half right.
 */
export function RadioGroup({ name, legend, options, value, onChange, className }: RadioGroupProps) {
  const id = useId();

  return (
    <fieldset className={cn("flex flex-col gap-2 text-start", className)}>
      <legend className="mb-1 text-sm font-medium">{legend}</legend>
      {options.map((option) => (
        <label key={option.value} htmlFor={`${id}-${option.value}`} className="flex items-center gap-2 text-sm">
          <input
            id={`${id}-${option.value}`}
            type="radio"
            name={name}
            value={option.value}
            checked={value === option.value}
            onChange={() => onChange(option.value)}
            className="size-4 border-neutral-300 text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-neutral-700"
          />
          {option.label}
        </label>
      ))}
    </fieldset>
  );
}

What CI checks

Quality contract
  • Bundled, minified and gzipped against its tier budget
  • Audited by axe in the state previewed above
  • Rendered through react-dom/server with no browser globals
  • Scanned for network calls, dangerous sinks, and unguarded animation