UNPKG

@loke/ui

Version:
146 lines (105 loc) 4.79 kB
--- name: radio-group type: core domain: forms requires: [loke-ui] description: > RadioGroup + RadioGroupItem + RadioGroupIndicator for single-selection groups. Roving focus, arrow-key navigation with auto-selection, Enter prevention per WAI-ARIA. Hidden native radio input for form participation. Standalone Radio primitive available. value prop required on every item. --- # Radio Group `@loke/ui/radio-group` — single-selection group built on `Primitive.div` + `RovingFocusGroup`. Each item wraps the standalone `Radio` primitive with a hidden native `<input type="radio">`. **Exports:** `RadioGroup`, `RadioGroupItem`, `RadioGroupIndicator`, `createRadioGroupScope` ## Setup ```tsx import { RadioGroup, RadioGroupItem, RadioGroupIndicator, } from "@loke/ui/radio-group"; import { Label } from "@loke/ui/label"; function PaymentMethod() { return ( <RadioGroup defaultValue="card" name="payment"> {[ { value: "card", label: "Credit card" }, { value: "paypal", label: "PayPal" }, { value: "bank", label: "Bank transfer" }, ].map(({ value, label }) => ( <div key={value} style={{ display: "flex", alignItems: "center", gap: 8 }}> <RadioGroupItem id={value} value={value}> <RadioGroupIndicator /> </RadioGroupItem> <Label htmlFor={value}>{label}</Label> </div> ))} </RadioGroup> ); } ``` `data-state` on `RadioGroupItem` and `RadioGroupIndicator`: `"checked"` | `"unchecked"` ## Core Patterns ### Controlled value ```tsx const [value, setValue] = useState("card"); <RadioGroup value={value} onValueChange={setValue} name="payment"> <RadioGroupItem value="card"><RadioGroupIndicator /></RadioGroupItem> <RadioGroupItem value="paypal"><RadioGroupIndicator /></RadioGroupItem> </RadioGroup> ``` ### Orientation and RTL `orientation` controls both the ARIA attribute and arrow-key direction. With `dir="rtl"`, left/right keys are mirrored automatically via `useDirection`. ```tsx <RadioGroup orientation="horizontal" dir="rtl" defaultValue="a"> <RadioGroupItem value="a"><RadioGroupIndicator /></RadioGroupItem> <RadioGroupItem value="b"><RadioGroupIndicator /></RadioGroupItem> </RadioGroup> ``` `orientation` values: `"vertical"` (default) | `"horizontal"` ### Standalone Radio Use `Radio` and `RadioIndicator` directly from `@loke/ui/radio-group` when you need a single radio outside a group context (e.g., inside a custom compound component). ```tsx import { Radio, RadioIndicator } from "@loke/ui/radio-group"; // Radio manages its own checked/onCheck props <Radio checked={isSelected} onCheck={() => setSelected(true)} name="solo" value="option-a" > <RadioIndicator /> </Radio> ``` Note: `Radio` is exported from the `radio-group` subpath, not a separate subpath. ## Common Mistakes ### 1. Using Enter to select — it is intentionally blocked Enter key is prevented on `RadioGroupItem` per the WAI-ARIA radio group pattern. Arrow keys navigate *and* select simultaneously via the roving focus mechanism. **Wrong:** Adding an `onKeyDown` handler that triggers selection on `"Enter"`. **Correct:** Arrow keys are the only keyboard selection mechanism. Do not add Enter-key selection logic. Source: `src/components/radio-group/radio-group.tsx` — `if (event.key === "Enter") event.preventDefault()` ### 2. Expecting to uncheck a selected item Radio buttons cannot be unchecked within a group once selected. `onCheck` on `Radio` only fires when `checked` is `false` — clicking an already-checked item does nothing. **Wrong:** Adding toggle logic expecting `onValueChange` to receive `undefined` when re-clicking the current value. **Correct:** Selection can only move to a different item. For optional selection, use a Checkbox or add a separate "Clear" control that resets state programmatically via `setValue(undefined)` on the controlled state. Source: `src/components/radio-group/radio.tsx` — `if (!checked) onCheck?.()` ### 3. Missing value prop on RadioGroupItem `RadioGroupItem` requires a `value` prop to identify itself within the group. Without it, the group's selection tracking breaks and no native `<input>` value is submitted. **Wrong:** ```tsx <RadioGroupItem> <RadioGroupIndicator /> </RadioGroupItem> ``` **Correct:** ```tsx <RadioGroupItem value="option-a"> <RadioGroupIndicator /> </RadioGroupItem> ``` Source: `src/components/radio-group/radio-group.tsx` — `context.value === itemProps.value` comparison ## Cross-references - **Label** (`@loke/ui/label`) — associate labels with each `RadioGroupItem` via `htmlFor` - **Checkbox** (`@loke/ui/checkbox`) — for multi-select or indeterminate scenarios - **Choosing the Right Component**RadioGroup vs Checkbox decision guide