Radio

A control for choosing exactly one option from a set.

Installation

bash
npx @smicolon/fasla-ui@latest add radio

Run init once per project before adding components. See Installation.

Preview

Variants

The layout variant wraps the control and its text in a bordered card, so the whole card becomes the target. Use it when the options carry a description; use the default when they are single words in a tight list.

Sizes

States

Hover and focus are real CSS states rather than props — hover an option, or tab to it, to see them. Selection is carried by the dot alone; the card chrome does not change.

Accessibility

  • Renders a native <input type="radio">, so arrow-key navigation and form submission work without extra code.
  • Options sharing a name form one group, and only one can be selected.
  • description is linked with aria-describedby; the control graphic is hidden from assistive technology.
  • Without a label or description, pass an aria-label.
  • Direction is inherited from dir, never a prop, so the control can never disagree with the page.

Props

PropTypeDefaultDescription
variant"default" | "layout""default"A plain control or a bordered card.
size"sm" | "md" | "lg""md"Control size.
checkedboolean—Selected state, with onChange.
disabledbooleanfalseDisables the option and dims it.
labelstring—Label text.
descriptionstring—Secondary line below the label.
namestring—Groups radios into one set.
valuestring—Value submitted with the form.
onChange(event) => void—Called when the option is picked.
classNamestring—Extra classes for the component.

Usage

A group — options share a name

tsx
import { Radio } from "@/components/ui/radio"

export function DeliveryOptions() {
  return (
    <fieldset className="flex flex-col gap-3">
      <legend className="mb-2 text-sm font-medium">Delivery</legend>
      <Radio name="delivery" value="standard" label="Standard" defaultChecked />
      <Radio name="delivery" value="express" label="Express" />
    </fieldset>
  )
}

With a description

tsx
import { Radio } from "@/components/ui/radio"

export function DeliveryOptions() {
  return (
    <fieldset className="flex flex-col gap-3">
      <legend className="mb-2 text-sm font-medium">Delivery</legend>
      <Radio
        name="delivery"
        value="standard"
        label="Standard"
        description="3–5 business days"
        defaultChecked
      />
      <Radio
        name="delivery"
        value="courier"
        label="Same-day courier"
        description="Arrives before 6pm"
      />
    </fieldset>
  )
}

Bordered card layout

tsx
import { Radio } from "@/components/ui/radio"

export function DeliveryCards() {
  return (
    <fieldset className="flex flex-col gap-3">
      <legend className="mb-2 text-sm font-medium">Delivery</legend>
      <Radio variant="layout" name="delivery" value="standard" label="Standard" defaultChecked />
      <Radio variant="layout" name="delivery" value="express" label="Express" />
    </fieldset>
  )
}

Controlled

tsx
"use client"

import { useState } from "react"
import { Radio } from "@/components/ui/radio"

export function ControlledDeliveryOptions() {
  const [delivery, setDelivery] = useState("standard")

  return (
    <fieldset className="flex flex-col gap-3">
      <legend className="mb-2 text-sm font-medium">Delivery</legend>
      <Radio
        name="delivery"
        value="standard"
        label="Standard"
        checked={delivery === "standard"}
        onChange={(e) => setDelivery(e.target.value)}
      />
      <Radio
        name="delivery"
        value="express"
        label="Express"
        checked={delivery === "express"}
        onChange={(e) => setDelivery(e.target.value)}
      />
    </fieldset>
  )
}

Selected: standard