Components / Inputs & Forms

Radio

Select one option from a mutually exclusive group.

Vertical

Horizontal

Disabled

Source RadioDemo.tsx

The exact code behind the live demo above. Fetch it raw at /components/source/radio.txt.

import { useState } from "react";

function Section({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <div className="mb-8">
      <h3 className="text-sm font-bold text-[var(--flux-heading)] mb-3">{title}</h3>
      <div className="p-6 rounded border border-[var(--flux-grey-100)] bg-[var(--flux-surface)]">
        {children}
      </div>
    </div>
  );
}

function RadioOption({
  label,
  selected,
  onSelect,
  disabled = false,
}: {
  label: string;
  selected: boolean;
  onSelect: () => void;
  disabled?: boolean;
}) {
  return (
    <label
      className={`flex items-center gap-2.5 select-none ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}`}
    >
      <button
        type="button"
        role="radio"
        aria-checked={selected}
        disabled={disabled}
        onClick={onSelect}
        className={`
          flex items-center justify-center w-[18px] h-[18px] rounded-full shrink-0
          transition-colors duration-150
          ${
            selected
              ? "border-2 border-[var(--flux-primary-400)]"
              : "border border-[var(--flux-grey-200)] bg-[var(--flux-surface)]"
          }
          ${disabled ? "pointer-events-none" : ""}
        `}
      >
        {selected && <span className="block w-2 h-2 rounded-full bg-[var(--flux-primary-400)]" />}
      </button>
      <span className="text-sm text-[var(--flux-black)]">{label}</span>
    </label>
  );
}

const options = ["Economy", "Business", "First Class"] as const;

export default function RadioDemo() {
  const [vertical, setVertical] = useState<string>("Economy");
  const [horizontal, setHorizontal] = useState<string>("Economy");

  return (
    <div>
      <Section title="Vertical">
        <div className="flex flex-col gap-3">
          {options.map((opt) => (
            <RadioOption key={opt} label={opt} selected={vertical === opt} onSelect={() => setVertical(opt)} />
          ))}
        </div>
      </Section>

      <Section title="Horizontal">
        <div className="flex items-center gap-6">
          {options.map((opt) => (
            <RadioOption key={opt} label={opt} selected={horizontal === opt} onSelect={() => setHorizontal(opt)} />
          ))}
        </div>
      </Section>

      <Section title="Disabled">
        <RadioOption label="Disabled option" selected={false} onSelect={() => {}} disabled />
      </Section>
    </div>
  );
}