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
valuestring—Controlled selected value
defaultValuestring—Uncontrolled initial value
onChange(value: string) => void—Called 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
labelstring—Group label rendered above
hintstring—Helper text rendered below
errorstring—Error 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
classNamestring—Extra class on the root element
styleCSSProperties—Inline style on the root element
childrenReactNode—One or more RadioItem elements

API — RadioItem #

PropTypeDefaultDescription
valuestring—Value this item represents (required)
labelReactNode—Label text (required)
descriptionReactNode—Secondary description shown below the label
disabledbooleanfalseDisable this specific item
classNamestring—Extra class on the item wrapper
styleCSSProperties—Inline style on the item wrapper

API — useRadioGroup hook #

OptionTypeDefaultDescription
valuestring—Controlled value
defaultValuestring—Uncontrolled initial value
onChange(value: string) => void—Called 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