react ~4 KB 0 deps v0.1.0 ↗ GitHub ↗

@mshafiqyajid/react-radio

Headless radio group hook + styled component. Three variants (default, card, button-group), four tones, three sizes, horizontal/vertical orientation, full keyboard navigation (arrow keys), accessible with ARIA.

Playground #

Preferred plan
Choose an option
Props
TSX
import { RadioGroupStyled, RadioItem } from "@mshafiqyajid/react-radio/styled";
import "@mshafiqyajid/react-radio/styles.css";

<RadioGroupStyled />

Install #

npm install @mshafiqyajid/react-radio

Quick start #

import { RadioGroupStyled, RadioItem } from "@mshafiqyajid/react-radio/styled";
import "@mshafiqyajid/react-radio/styles.css";

const [plan, setPlan] = useState("pro");

<RadioGroupStyled value={plan} onChange={setPlan} label="Choose a plan">
  <RadioItem value="starter" label="Starter" description="Up to 5 projects" />
  <RadioItem value="pro" label="Pro" description="Unlimited projects" />
  <RadioItem value="enterprise" label="Enterprise" description="Custom limits" />
</RadioGroupStyled>

Card variant #

The card variant wraps each item in a bordered card. The selected card highlights with the chosen tone colour.

<RadioGroupStyled
  defaultValue="pro"
  variant="card"
  tone="primary"
  label="Choose a plan"
>
  <RadioItem value="starter" label="Starter" description="Up to 5 projects" />
  <RadioItem value="pro" label="Pro" description="Unlimited projects" />
  <RadioItem value="enterprise" label="Enterprise" description="Custom limits" />
</RadioGroupStyled>

Button-group variant #

The button-group variant renders items inline as a segmented control. The selected item receives a filled background.

<RadioGroupStyled
  defaultValue="month"
  variant="button-group"
  tone="primary"
  label="Billing period"
>
  <RadioItem value="month" label="Monthly" />
  <RadioItem value="year" label="Annually" />
</RadioGroupStyled>

Horizontal orientation #

Pass orientation="horizontal" to lay out items side by side (works with all variants).

<RadioGroupStyled
  defaultValue="a"
  orientation="horizontal"
  label="Priority"
>
  <RadioItem value="a" label="Low" />
  <RadioItem value="b" label="Medium" />
  <RadioItem value="c" label="High" />
</RadioGroupStyled>

Headless #

Use useRadioGroup directly for full styling control. The hook handles value state, ARIA attributes, and keyboard navigation.

import { useRadioGroup } from "@mshafiqyajid/react-radio";

const options = [
  { value: "a", label: "Option A" },
  { value: "b", label: "Option B" },
  { value: "c", label: "Option C" },
];

function MyRadioGroup() {
  const { groupProps, getItemProps, value } = useRadioGroup({
    defaultValue: "a",
    onChange: (v) => console.log(v),
  });

  return (
    <div {...groupProps} className="my-group">
      {options.map((opt) => (
        <div key={opt.value} {...getItemProps(opt.value)} className="my-item">
          <span className={`my-circle ${value === opt.value ? "my-circle--checked" : ""}`} />
          <span>{opt.label}</span>
        </div>
      ))}
    </div>
  );
}

API — RadioGroupStyled #

PropTypeDefaultDescription
valuestringControlled selected value
defaultValuestringUncontrolled initial value
onChange(value: string) => voidCalled when selection changes
namestringautoNative form name for radio inputs
variant"default" | "card" | "button-group""default"Visual style of the group
size"sm" | "md" | "lg""md"Size
tone"neutral" | "primary" | "success" | "danger""neutral"Accent colour when selected
orientation"vertical" | "horizontal""vertical"Layout direction
labelstringGroup label rendered above
hintstringHelper text rendered below
errorstringError message — applies danger tone and renders below
requiredbooleanfalseMark group as required (adds asterisk)
disabledbooleanfalseDisable all items in the group
invalidbooleanfalseForce invalid state without inline error text
classNamestringExtra class on the root element
styleCSSPropertiesInline style on the root element
childrenReactNodeOne or more RadioItem elements

API — RadioItem #

PropTypeDefaultDescription
valuestringValue this item represents (required)
labelReactNodeLabel text (required)
descriptionReactNodeSecondary description shown below the label
disabledbooleanfalseDisable this specific item
classNamestringExtra class on the item wrapper
styleCSSPropertiesInline style on the item wrapper

API — useRadioGroup hook #

OptionTypeDefaultDescription
valuestringControlled value
defaultValuestringUncontrolled initial value
onChange(value: string) => voidCalled when selection changes
namestringautoForm name
disabledbooleanfalseDisable all items
requiredbooleanfalseMark group as required
invalidbooleanfalseMark group as invalid

Returns { groupProps, getItemProps, value, setValue, name, groupId }. Spread groupProps on the group container and getItemProps(value) on each item element.

Keyboard #

KeyAction
Arrow Down / Arrow RightFocus and select the next non-disabled item
Arrow Up / Arrow LeftFocus and select the previous non-disabled item
SpaceSelect the focused item
TabMove focus out of the group
Edit this page on GitHub