elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Forms / RadioGroup

RadioGroup

One choice from a set: native radios sharing a name, with arrow-key movement.

Basic

value is two-way. Arrow keys move and check; Tab enters on the checked radio.

value: comfortable, changes: 0
Code
<RadioGroup value={state.density} attrs={{ "aria-label": "Density" }}>
  <Label><RadioGroupItem value="default" /> Default</Label>
  <Label><RadioGroupItem value="comfortable" /> Comfortable</Label>
  <Label><RadioGroupItem value="compact" /> Compact</Label>
</RadioGroup>

Usage

import { RadioGroup, RadioGroupItem } from "@elements/ui/radio-group";

<RadioGroup value={form.density} attrs={{ "aria-label": "Density" }}>
  <Label><RadioGroupItem value="default" /> Default</Label>
  <Label><RadioGroupItem value="compact" /> Compact</Label>
</RadioGroup>

<RadioGroup value={form.plan} options={PLANS} orientation="horizontal" />

Examples

With descriptions

Each item is labelled and described by id.

For individuals and small teams.

For growing businesses.

For large teams and enterprises.

Code
<RadioGroup value={state.plan} attrs={{ "aria-label": "Plan" }}>
  <div class="option">
    <RadioGroupItem value="plus" attrs={{ id: "plus", "aria-describedby": "plus-hint" }} />
    <div>
      <Label htmlFor="plus">Plus</Label>
      <p class="hint" id="plus-hint">For individuals and small teams.</p>
    </div>
  </div>
  ...
</RadioGroup>

Options as data, horizontal

options renders a labelled item each; orientation lays them in a row.

value: m
Code
const SIZES = [{ value: "s", label: "Small" }, ...];

<RadioGroup value={state.size} options={SIZES} orientation="horizontal" attrs={{ "aria-label": "Size" }} />

No wrap, disabled item

loop off stops at the ends; arrows skip the disabled Quarterly and Lifetime.

value: monthly
Code
<RadioGroup value={state.edge} options={PLANS} loop={false} />

Disabled group

disabled on the group disables every item.

Read only

readOnly keeps the choice under a click, Space or an arrow. The radios stay focusable and arrows still move focus. readOnly on one RadioGroupItem locks only that item. form joins the second group to a form it sits outside.

Code
<RadioGroup value="b" readOnly attrs={{ "aria-label": "Region" }}
            options={[{ value: "a", label: "United States" }, { value: "b", label: "European Union" }]} />

<RadioGroup value={state.storage} name="storage" form="notify-form" attrs={{ "aria-label": "Storage" }}>
  <Label><RadioGroupItem value="standard" /> Standard</Label>
  <Label><RadioGroupItem value="archive" readOnly /> Archive (locked)</Label>
</RadioGroup>

Required in a form

Nothing chosen blocks the submit. invalid marks every item.

submitted: 0
Code
<form onsubmit={() => save(state)}>
  <RadioGroup value={state.notify} name="notify" required invalid={state.invalid}>
    ...
  </RadioGroup>
  <Button type="submit" variant="primary">Save</Button>
  <Button type="reset">Reset</Button>
</form>

Required item

required on one RadioGroupItem makes the browser require a choice, as native radios sharing a name do.

Code
<RadioGroup value={state.contact} name="contact">
  <Label><RadioGroupItem value="email" required /> Email</Label>
  <Label><RadioGroupItem value="phone" /> Phone</Label>
</RadioGroup>

No name

Without a name the radios still group, one tab stop and one choice, but the form submits nothing for them. The bound value carries the choice.

pace: steady
Code
<form>
  <RadioGroup value={state.pace} attrs={{ "aria-label": "Pace" }}>
    <Label><RadioGroupItem value="steady" /> Steady</Label>
    <Label><RadioGroupItem value="fast" /> Fast</Label>
  </RadioGroup>
</form>

Fieldset

A FieldSet and legend name the group; each item is a horizontal Field.

Subscription Plan

Yearly and lifetime plans offer significant savings.

Code
<FieldSet>
  <FieldLegend variant="label">Subscription Plan</FieldLegend>
  <FieldDescription>Yearly and lifetime plans offer significant savings.</FieldDescription>
  <RadioGroup value={state.subscription} name="subscription">
    <Field orientation="horizontal" label="Monthly ($9.99/month)" reserveError={false}>
      <RadioGroupItem value="monthly" />
    </Field>
    ...
  </RadioGroup>
</FieldSet>

Choice cards

A FieldLabel wrapping a Field makes the whole card choose the item.

plan: plus
Code
<RadioGroup value={state.tier} attrs={{ "aria-label": "Plan" }}>
  <FieldLabel>
    <Field orientation="horizontal" reserveError={false}>
      <FieldContent>
        <FieldTitle>Plus</FieldTitle>
        <FieldDescription>For individuals and small teams.</FieldDescription>
      </FieldContent>
      <RadioGroupItem value="plus" />
    </Field>
  </FieldLabel>
  ...
</RadioGroup>

Rating

The APG rating example: one radio per star. Arrows change the rating and every star up to it fills.

rating: none
Code
<RadioGroup value={state.rating} orientation="horizontal" attrs={{ "aria-label": "Rating" }}>
  <label e:for={n of [1, 2, 3, 4, 5]} class="star">
    <RadioGroupItem value={String(n)}
                    attrs={{ "aria-label": `${n} stars`, "data-filled": Number(state.rating) >= n ? "" : undefined }} />
    <Icon name="star" />
  </label>
</RadioGroup>

Cancel a change

onValueChange runs before the choice changes and gets details second; details.cancel() keeps the checked radio, for a click, Space or an arrow.

value: free, attempts: 0, last: none
Code
<RadioGroup value={guard.value} onValueChange={(value, details) => {
  if (!guard.allowed) details.cancel();
}}>
  <Label><RadioGroupItem value="free" /> Free</Label>
  <Label><RadioGroupItem value="pro" /> Pro</Label>
  <Label><RadioGroupItem value="team" /> Team</Label>
</RadioGroup>

Right to left

ArrowLeft moves to the next radio in a right-to-left group.

value: a
Reference RadioGroup in the manual Every part, attribute, slot, key and data attribute.
Prev PasswordInputNext Select
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026