Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Radio Group

A set of checkable buttons — known as radio buttons — where no more than one of the buttons can be checked at a time.

Playground

—value
—description
—disabled
—aria-invalid
Usage
Import RadioGroup and RadioGroupItem from the design system and wrap each item in a Field with a FieldLabel. The hit area extends invisibly past the 16px circle, so the target stays comfortable on touch screens.
import {
  RadioGroup,
  RadioGroupItem,
} from "@workspace/ui/components/radio-group"
import { Field, FieldLabel } from "@workspace/ui/components/field"

<RadioGroup defaultValue="comfortable">
  <Field orientation="horizontal">
    <RadioGroupItem value="default" id="density-default" />
    <FieldLabel htmlFor="density-default">Default</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="comfortable" id="density-comfortable" />
    <FieldLabel htmlFor="density-comfortable">Comfortable</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="compact" id="density-compact" />
    <FieldLabel htmlFor="density-compact">Compact</FieldLabel>
  </Field>
</RadioGroup>
Description
Wrap the label in FieldContent and add a FieldDescription for helper text — the radio stays aligned with the first line of the label.

Get notified for every message in every channel.

Only get notified when someone mentions you directly.

<RadioGroup defaultValue="mentions">
  <Field orientation="horizontal">
    <RadioGroupItem value="all" id="notify-all" />
    <FieldContent>
      <FieldLabel htmlFor="notify-all">All new messages</FieldLabel>
      <FieldDescription>
        Get notified for every message in every channel.
      </FieldDescription>
    </FieldContent>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="mentions" id="notify-mentions" />
    <FieldContent>
      <FieldLabel htmlFor="notify-mentions">Mentions only</FieldLabel>
      <FieldDescription>
        Only get notified when someone mentions you directly.
      </FieldDescription>
    </FieldContent>
  </Field>
</RadioGroup>
Choice Cards
Wrap the entire Field in a FieldLabel to turn each option into a clickable card — the checked card picks up a primary border and background tint automatically.
<RadioGroup defaultValue="pro">
  <FieldLabel htmlFor="plan-starter">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>Starter</FieldTitle>
        <FieldDescription>
          For personal projects and experiments.
        </FieldDescription>
      </FieldContent>
      <RadioGroupItem value="starter" id="plan-starter" />
    </Field>
  </FieldLabel>
  <FieldLabel htmlFor="plan-pro">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>Pro</FieldTitle>
        <FieldDescription>
          For small teams shipping to production.
        </FieldDescription>
      </FieldContent>
      <RadioGroupItem value="pro" id="plan-pro" />
    </Field>
  </FieldLabel>
</RadioGroup>
Fieldset
Group the options inside a FieldSet with a FieldLegend so assistive technology announces the question with each option. Set variant="label" on the legend so it renders at label size.
Where should we ship your order?

Delivery speed depends on the destination.

<FieldSet>
  <FieldLegend variant="label">Where should we ship your order?</FieldLegend>
  <FieldDescription>
    Delivery speed depends on the destination.
  </FieldDescription>
  <RadioGroup defaultValue="home">
    <Field orientation="horizontal">
      <RadioGroupItem value="home" id="ship-home" />
      <FieldLabel htmlFor="ship-home" className="font-normal">
        Home address
      </FieldLabel>
    </Field>
    <Field orientation="horizontal">
      <RadioGroupItem value="office" id="ship-office" />
      <FieldLabel htmlFor="ship-office" className="font-normal">
        Office address
      </FieldLabel>
    </Field>
    <Field orientation="horizontal">
      <RadioGroupItem value="pickup" id="ship-pickup" />
      <FieldLabel htmlFor="ship-pickup" className="font-normal">
        Pickup point
      </FieldLabel>
    </Field>
  </RadioGroup>
</FieldSet>
Controlled
Use defaultValue for uncontrolled groups, or value and onValueChange to control the selection — the callback receives the newly selected item's value string.

Notifying about: Direct messages and mentions

const [value, setValue] = React.useState("mentions")

<RadioGroup value={value} onValueChange={setValue}>
  <Field orientation="horizontal">
    <RadioGroupItem value="all" id="notify-all" />
    <FieldLabel htmlFor="notify-all">All new messages</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="mentions" id="notify-mentions" />
    <FieldLabel htmlFor="notify-mentions">
      Direct messages and mentions
    </FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="none" id="notify-none" />
    <FieldLabel htmlFor="notify-none">Nothing</FieldLabel>
  </Field>
</RadioGroup>
Disabled
Set disabled on the RadioGroup to disable every option, or on a single RadioGroupItem to disable just that option. Add data-disabled to the Field so the label dims with the circle.
<RadioGroup defaultValue="standard">
  <Field orientation="horizontal">
    <RadioGroupItem value="standard" id="tier-standard" />
    <FieldLabel htmlFor="tier-standard">Standard delivery</FieldLabel>
  </Field>
  <Field orientation="horizontal" data-disabled>
    <RadioGroupItem value="express" id="tier-express" disabled />
    <FieldLabel htmlFor="tier-express">
      Express delivery (unavailable)
    </FieldLabel>
  </Field>
</RadioGroup>
Invalid
Set aria-invalid on each RadioGroupItem and data-invalid on the field wrapper to show the invalid styles — a destructive border and ring on the circle, and destructive text on the label. Once an option is checked, its border returns to primary.
<RadioGroup>
  <Field orientation="horizontal" data-invalid>
    <RadioGroupItem value="yes" id="consent-yes" aria-invalid />
    <FieldLabel htmlFor="consent-yes">Yes, I agree</FieldLabel>
  </Field>
  <Field orientation="horizontal" data-invalid>
    <RadioGroupItem value="no" id="consent-no" aria-invalid />
    <FieldLabel htmlFor="consent-no">No, I disagree</FieldLabel>
  </Field>
</RadioGroup>
RTL
Pass dir="rtl" to the RadioGroup as well as the layout wrapper — Radix stamps its own direction on the group (defaulting to LTR), and the prop keeps the indicator centered and reverses arrow-key navigation. Alternatively, wrap the app in the design system's DirectionProvider.
<FieldGroup dir="rtl">
  <RadioGroup dir="rtl" defaultValue="monthly">
    <Field orientation="horizontal">
      <RadioGroupItem value="monthly" id="billing-monthly" />
      <FieldLabel htmlFor="billing-monthly">فوترة شهرية</FieldLabel>
    </Field>
    <Field orientation="horizontal">
      <RadioGroupItem value="yearly" id="billing-yearly" />
      <FieldLabel htmlFor="billing-yearly">فوترة سنوية</FieldLabel>
    </Field>
  </RadioGroup>
</FieldGroup>
API Reference
RadioGroup wraps the Radix Radio Group primitive and accepts all of its props — uncontrolled via defaultValue or controlled via value + onValueChange. Inside a form the group submits the checked item's value under name like a native radio set. Arrow keys move focus and selection between items.
ComponentPropDefault
RadioGroupvalue?: string—
RadioGroupdefaultValue?: string—
RadioGrouponValueChange?: (value: string) => void—
RadioGroupdisabled?: booleanfalse
RadioGrouprequired?: booleanfalse
RadioGroupname?: string—
RadioGrouporientation?: "horizontal" | "vertical"—
RadioGrouploop?: booleantrue
RadioGroupItemvalue: stringrequired
RadioGroupItemdisabled?: booleanfalse

Tokens used

6 design tokens consumed by the Radio Group component.

  • --primary

    Primary

    data-checked:border-primarydata-checked:bg-primarydark:data-checked:bg-primaryaria-invalid:aria-checked:border-primary
  • --primary-foreground

    Primary Foreground

    data-checked:text-primary-foregroundbg-primary-foreground
  • --input

    Input

    border-inputdark:bg-input/30
  • --ring

    Ring

    focus-visible:border-ringfocus-visible:ring-ring/50
  • --destructive

    Destructive

    aria-invalid:border-destructivearia-invalid:ring-destructive/20dark:aria-invalid:border-destructive/50dark:aria-invalid:ring-destructive/40
  • --spacing

    Spacing unit (4px)

    size-4size-2gap-3after:-inset-x-3after:-inset-y-2