@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 #
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 #
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Controlled selected value |
| defaultValue | string | — | Uncontrolled initial value |
| onChange | (value: string) => void | — | Called when selection changes |
| name | string | auto | Native 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 |
| label | string | — | Group label rendered above |
| hint | string | — | Helper text rendered below |
| error | string | — | Error message — applies danger tone and renders below |
| required | boolean | false | Mark group as required (adds asterisk) |
| disabled | boolean | false | Disable all items in the group |
| invalid | boolean | false | Force invalid state without inline error text |
| className | string | — | Extra class on the root element |
| style | CSSProperties | — | Inline style on the root element |
| children | ReactNode | — | One or more RadioItem elements |
API — RadioItem #
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Value this item represents (required) |
| label | ReactNode | — | Label text (required) |
| description | ReactNode | — | Secondary description shown below the label |
| disabled | boolean | false | Disable this specific item |
| className | string | — | Extra class on the item wrapper |
| style | CSSProperties | — | Inline style on the item wrapper |
API — useRadioGroup hook #
| Option | Type | Default | Description |
|---|---|---|---|
| value | string | — | Controlled value |
| defaultValue | string | — | Uncontrolled initial value |
| onChange | (value: string) => void | — | Called when selection changes |
| name | string | auto | Form name |
| disabled | boolean | false | Disable all items |
| required | boolean | false | Mark group as required |
| invalid | boolean | false | Mark group as invalid |
Returns { groupProps, getItemProps, value, setValue, name, groupId }. Spread groupProps on the group container and getItemProps(value) on each item element.
Keyboard #
| Key | Action |
|---|---|
| Arrow Down / Arrow Right | Focus and select the next non-disabled item |
| Arrow Up / Arrow Left | Focus and select the previous non-disabled item |
| Space | Select the focused item |
| Tab | Move focus out of the group |