Radio

free

Radio lets users pick a single option from a short, mutually exclusive set.

Preview
Dark
Billing plan

Choose the plan applied to this workspace.

import { useState } from 'react';
import Radio from '@/components/ui/Radio';

const plans = [
  { label: 'Starter', value: 'starter', hint: 'For solo projects' },
  { label: 'Team', value: 'team', hint: 'Shared workspaces' },
  { label: 'Business', value: 'business', hint: 'SSO and audit logs' },
];

export default function UsageDemo() {
  const [plan, setPlan] = useState('team');

  return (
    <div className="flex flex-col gap-4">
      <div>
        <div className="text-base font-semibold text-foreground">
          Billing plan
        </div>
        <p className="text-muted-foreground">
          Choose the plan applied to this workspace.
        </p>
      </div>

      <Radio.Group value={plan} onChange={setPlan} vertical>
        {plans.map((item) => (
          <Radio 
            key={item.value} 
            value={item.value}
            className="items-start"
            radioClass="mt-1"
          >
            <span className="flex flex-col">
              <span>{item.label}</span>
              <span className="text-xs text-muted-foreground">
                {item.hint}
              </span>
            </span>
          </Radio>
        ))}
      </Radio.Group>
    </div>
  );
}

Installation

Add this component with the NateUI CLI.

npx nateui@latest add Radio

Examples

Basic

Preview
Dark
import Radio from '@/components/ui/Radio';

export default function BasicDemo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Radio name="basic-demo" value="off">
        Unselected
      </Radio>
      <Radio name="basic-demo" value="on" defaultChecked>
        Selected by default
      </Radio>
    </div>
  );
}

Group

Preview
Dark
import { useState } from 'react';
import Radio from '@/components/ui/Radio';

const speeds = [
  { label: 'Standard', value: 'standard' },
  { label: 'Priority', value: 'priority' },
  { label: 'Overnight', value: 'overnight' },
];

export default function GroupDemo() {
  const [speed, setSpeed] = useState('priority');

  return (
    <Radio.Group value={speed} onChange={setSpeed}>
      {speeds.map((item) => (
        <Radio key={item.value} value={item.value}>
          {item.label}
        </Radio>
      ))}
    </Radio.Group>
  );
}

Vertical

Preview
Dark
import { useState } from 'react';
import Radio from '@/components/ui/Radio';

export default function VerticalDemo() {
  const [visibility, setVisibility] = useState('team');

  return (
    <Radio.Group vertical value={visibility} onChange={setVisibility}>
      <Radio value="private">Only me</Radio>
      <Radio value="team">Everyone on the team</Radio>
      <Radio value="public">Anyone with the link</Radio>
    </Radio.Group>
  );
}

States

Preview
Dark
import Radio from '@/components/ui/Radio';

export default function StatesDemo() {
  return (
    <div className="flex flex-col gap-4">
      <Radio name="states-demo" value="selected" defaultChecked>
        Active selection
      </Radio>
      <Radio name="states-demo" value="disabled" disabled>
        Disabled unselected
      </Radio>
      <Radio value="locked" checked disabled>
        Disabled selected
      </Radio>
      <Radio value="readonly" checked readOnly>
        Read-only selected
      </Radio>
    </div>
  );
}

Custom Color

Preview
Dark
import Radio from '@/components/ui/Radio';

export default function CustomColorDemo() {
  return (
    <div className="flex flex-col gap-4">
      <Radio value="healthy" defaultChecked radioClass="text-success">
        Healthy status
      </Radio>
      <Radio value="review" defaultChecked radioClass="text-warning">
        Needs attention
      </Radio>
      <Radio value="blocked" defaultChecked radioClass="text-destructive">
        Blocked deployment
      </Radio>
    </div>
  );
}

Controlled

Preview
Dark

Current access: write

import { useState } from 'react';
import Radio from '@/components/ui/Radio';

const levels = [
  { label: 'Read', value: 'read' },
  { label: 'Write', value: 'write' },
  { label: 'Admin', value: 'admin' },
];

export default function ControlledDemo() {
  const [access, setAccess] = useState('write');

  return (
    <div className="flex flex-col gap-4">
      <Radio.Group value={access} onChange={setAccess}>
        {levels.map((item) => (
          <Radio key={item.value} value={item.value}>
            {item.label}
          </Radio>
        ))}
      </Radio.Group>
      <p className="text-xs text-muted-foreground">
        Current access: <span className="font-medium">{access}</span>
      </p>
    </div>
  );
}

API

Radio exposes a single radio input and a compound Radio.Group for single-selection sets.

Radio

Radio accepts the props below plus native props for the underlying input.

PropDescriptionTypeDefault
checkedControls the selected state for standalone usage.boolean-
defaultCheckedInitial selected state for uncontrolled usage.boolean-
valueValue used by Radio.Group to match the selected option and passed through to the input.RadioValue-
radioClassClass names applied to the radio control, used for custom color or local styling.string-
childrenLabel content rendered beside the radio control.ReactNode-
classNameClass names applied to the root label.string-
disabledPrevents interaction and applies disabled styling. Inherited from the group when set there.booleanGroup disabled
readOnlyPrevents the radio from changing state on interaction.boolean-
nameName passed to the input, or inherited from the group.stringGroup name
onChangeCalled with this radio value and the input change event when it becomes selected.RadioChangeHandler-
labelRefRef forwarded to the root label element.Ref<HTMLLabelElement>-
refRef forwarded to the underlying radio input.Ref<HTMLInputElement>-
...nativeInputPropsNative props for the underlying radio input.NativeRadioInputProps-

Radio.Group

Radio.Group accepts the props below plus native props for the root div.

PropDescriptionTypeDefault
valueSelected value shared by the group; the matching radio is selected.RadioValue-
onChangeCalled with the next selected value and the source change event.RadioGroupChangeHandler-
radioClassClass names applied to every radio control in the group.string-
disabledDisables every radio in the group.boolean-
nameName shared by radio inputs inside the group.string-
verticalStacks group items vertically.booleanfalse
childrenRadio items rendered inside the group.ReactNode-
classNameClass names applied to the group root.string-
refRef forwarded to the group root.Ref<HTMLDivElement>-
...nativeDivPropsNative props for the group root div.NativeRadioGroupDivProps-

Types

type RadioValue = string;
type RadioChangeHandler = (
  value: RadioValue,
  event: ChangeEvent<HTMLInputElement>,
) => void;
type RadioGroupChangeHandler = (
  value: RadioValue,
  event: ChangeEvent<HTMLInputElement>,
) => void;
type NativeRadioInputProps = ComponentProps<'input'>;
type NativeRadioGroupDivProps = ComponentProps<'div'>;