Radio

Standalone radio buttons and radio group for single-select options — built with Tailwind CSS.

Preview

Basic radio group

import { RadioGroup } from '@/registry/ui-kit/tailwind-radio/react'

function Example() {
  return (
    <RadioGroup
      name="color"
      options={[
        { value: 'red', label: 'Red' },
        { value: 'blue', label: 'Blue' },
        { value: 'green', label: 'Green' },
      ]}
    />
  )
}

With descriptions

import { RadioGroup } from '@/registry/ui-kit/tailwind-radio/react'

function Example() {
  return (
    <RadioGroup
      name="plan"
      options={[
        { value: 'free', label: 'Free', description: 'Up to 5 projects.' },
        { value: 'pro', label: 'Pro', description: 'Unlimited projects.' },
      ]}
    />
  )
}

Component API

PropDefaultDescription
nameHTML name attribute shared by all radios in the group.
optionsArray of radio options for RadioGroup.
valueCurrently selected value (controlled).
onChangeCallback when selection changes.
labelText label for a single Radio input.
descriptionOptional helper text for a single Radio input.

Source Code

"use client";

import { forwardRef } from "react";

/* ── Radio ── */

interface RadioProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
  label: string;
  description?: string;
  className?: string;
}

const Radio = forwardRef<HTMLInputElement, RadioProps>(
  ({ label, description, className = "", ...props }, ref) => {
    return (
      <label className={`flex items-start gap-3 ${className}`}>
        <input
          ref={ref}
          type="radio"
          className="h-4 w-4 shrink-0 cursor-pointer border-zinc-300 text-indigo-600 focus:ring-indigo-500/20 dark:border-zinc-600 dark:bg-zinc-900"
          {...props}
        />
        <span className="select-none">
          <span className="block text-sm font-medium text-zinc-900 dark:text-zinc-100">
            {label}
          </span>
          {description && (
            <span className="block text-sm text-zinc-500 dark:text-zinc-400">
              {description}
            </span>
          )}
        </span>
      </label>
    );
  }
);
Radio.displayName = "Radio";

/* ── RadioGroup ── */

interface RadioOption {
  value: string;
  label: string;
  description?: string;
}

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

function RadioGroup({ name, options, value, onChange, className = "" }: RadioGroupProps) {
  return (
    <div className={`space-y-3 ${className}`} role="radiogroup">
      {options.map((option) => (
        <Radio
          key={option.value}
          name={name}
          value={option.value}
          label={option.label}
          description={option.description}
          checked={value === option.value}
          onChange={() => onChange?.(option.value)}
        />
      ))}
    </div>
  );
}

export { Radio, RadioGroup };
export type { RadioOption, RadioGroupProps };
export default Radio;