Skip to content
UI

Radio Group

Picks exactly one option from a small set, with every option visible at once.

@zenoxai/ui/components/radio-groupBase UI Markdown
<RadioGroup
  defaultValue="daily"
  aria-label="Scan frequency"
  className="w-fit"
>
  <Field orientation="horizontal">
    <RadioGroupItem value="hourly" id="frequency-hourly" />
    <FieldLabel htmlFor="frequency-hourly">Hourly</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="daily" id="frequency-daily" />
    <FieldLabel htmlFor="frequency-daily">Daily</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="weekly" id="frequency-weekly" />
    <FieldLabel htmlFor="frequency-weekly">Weekly</FieldLabel>
  </Field>
</RadioGroup>

Usage

import { Field, FieldLabel } from "@zenoxai/ui/components/field"
import { RadioGroup, RadioGroupItem } from "@zenoxai/ui/components/radio-group"

<RadioGroup defaultValue="daily" aria-label="Scan frequency">
  <Field orientation="horizontal">
    <RadioGroupItem value="hourly" id="frequency-hourly" />
    <FieldLabel htmlFor="frequency-hourly">Hourly</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="daily" id="frequency-daily" />
    <FieldLabel htmlFor="frequency-daily">Daily</FieldLabel>
  </Field>
</RadioGroup>

Use a radio group for two to five options that the user should compare before choosing, and when one option is always selected. Set defaultValue so the group never starts empty.

For more options, or when the choice is secondary, use a Select. For options that can be combined, use Checkboxes. For a choice that changes a view right away, such as a filter, a Toggle Group is more compact.

Lay out each option as a horizontal Field with a FieldLabel pointing at the item’s id. The group is a vertical grid with an 8px gap; override it with className.

Examples

With a description

Put the group in a FieldSet with a FieldLegend that names the choice, and give each option a FieldDescription in a FieldContent.

When an alert is resolved

Only the assigned analyst gets an email.

Everyone on the tenant's SOC team gets an email.

The change appears in the alert history only.

<FieldSet className="max-w-sm">
  <FieldLegend variant="label">When an alert is resolved</FieldLegend>
  <RadioGroup defaultValue="notify-owner">
    <Field orientation="horizontal">
      <RadioGroupItem value="notify-owner" id="resolve-owner" />
      <FieldContent>
        <FieldLabel htmlFor="resolve-owner">Notify the owner</FieldLabel>
        <FieldDescription>
          Only the assigned analyst gets an email.
        </FieldDescription>
      </FieldContent>
    </Field>
    <Field orientation="horizontal">
      <RadioGroupItem value="notify-team" id="resolve-team" />
      <FieldContent>
        <FieldLabel htmlFor="resolve-team">Notify the team</FieldLabel>
        <FieldDescription>
          Everyone on the tenant's SOC team gets an email.
        </FieldDescription>
      </FieldContent>
    </Field>
    <Field orientation="horizontal">
      <RadioGroupItem value="silent" id="resolve-silent" />
      <FieldContent>
        <FieldLabel htmlFor="resolve-silent">Do not notify</FieldLabel>
        <FieldDescription>
          The change appears in the alert history only.
        </FieldDescription>
      </FieldContent>
    </Field>
  </RadioGroup>
</FieldSet>

Choice cards

Wrap each option’s Field in a FieldLabel to turn it into a bordered card. The whole card is clickable, and the selected card is highlighted. Use FieldTitle for the option name inside the card, since the card itself is the label.

<RadioGroup
  defaultValue="external"
  aria-label="Scan type"
  className="max-w-sm"
>
  <FieldLabel htmlFor="scan-external">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>External scan</FieldTitle>
        <FieldDescription>
          Internet-facing domains and IPs. About 10 minutes.
        </FieldDescription>
      </FieldContent>
      <RadioGroupItem value="external" id="scan-external" />
    </Field>
  </FieldLabel>
  <FieldLabel htmlFor="scan-internal">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>Internal scan</FieldTitle>
        <FieldDescription>
          Hosts behind the network agent. About 1 hour.
        </FieldDescription>
      </FieldContent>
      <RadioGroupItem value="internal" id="scan-internal" />
    </Field>
  </FieldLabel>
</RadioGroup>

Disabled

disabled on a RadioGroupItem disables one option; on the RadioGroup it disables them all. Set data-disabled on the option’s Field to dim its label. Say in the label why the option is not available.

<RadioGroup defaultValue="30" aria-label="Log retention" className="w-fit">
  <Field orientation="horizontal">
    <RadioGroupItem value="30" id="retention-30" />
    <FieldLabel htmlFor="retention-30">30 days</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="90" id="retention-90" />
    <FieldLabel htmlFor="retention-90">90 days</FieldLabel>
  </Field>
  <Field orientation="horizontal" data-disabled>
    <RadioGroupItem value="365" id="retention-365" disabled />
    <FieldLabel htmlFor="retention-365">
      1 year (Enterprise plan)
    </FieldLabel>
  </Field>
</RadioGroup>

Controlled

Pass value and onValueChange. RadioGroup is not generic, so onValueChange receives unknown; cast it to the type of your values.

Value: open

function RadioGroupControlled() {
  const [view, setView] = React.useState("open")

  return (
    <div className="grid justify-items-center gap-3">
      <RadioGroup
        value={view}
        onValueChange={(value) => setView(value as string)}
        aria-label="Alerts to show"
        className="flex w-fit gap-4"
      >
        <Field orientation="horizontal" className="w-auto">
          <RadioGroupItem value="open" id="view-open" />
          <FieldLabel htmlFor="view-open">Open</FieldLabel>
        </Field>
        <Field orientation="horizontal" className="w-auto">
          <RadioGroupItem value="mine" id="view-mine" />
          <FieldLabel htmlFor="view-mine">Assigned to me</FieldLabel>
        </Field>
        <Field orientation="horizontal" className="w-auto">
          <RadioGroupItem value="all" id="view-all" />
          <FieldLabel htmlFor="view-all">All</FieldLabel>
        </Field>
      </RadioGroup>
      <p className="text-sm text-text-secondary">Value: {view}</p>
    </div>
  )
}

Accessibility

  • The group has role="radiogroup" and needs a name: aria-label, or aria-labelledby pointing at a visible heading. A FieldLegend names the surrounding FieldSet, not the group itself.
  • Tab moves focus into the group to the selected option and out again in one step. Arrow keys move between options and select them.
  • Each item renders a <span role="radio"> with a hidden <input> beside it. The id goes to the hidden input, so FieldLabel htmlFor names the item and selects it on click.
  • name on RadioGroup submits the selected value with a native form.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

RadioGroup

RadioGroup picks exactly one option from a short list that stays in view, such as a plan or a frequency. For a long list, use Select; for an on/off choice, Checkbox or Switch; for a compact segmented control, ToggleGroup.

PropTypeDefault
defaultValueany

The uncontrolled value of the radio button that should be initially selected.

-
disabledboolean

Whether the component should ignore user interaction.

false
formstring

Identifies the form that owns the radio inputs. Useful when the radio group is rendered outside the form.

-
inputRefRef<HTMLInputElement>

A ref to access the hidden input element.

-
namestring

Identifies the field when a form is submitted.

-
onValueChange((value: any, eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element; }) => void)

Callback fired when the value changes.

-
readOnlyboolean

Whether the user should be unable to select a different radio button in the group.

false
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, RadioGroupState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
requiredboolean

Whether the user must choose a value before submitting a form.

false
valueany

The controlled value of the radio item that should be currently selected.

-

RadioGroupItem

PropTypeDefault
disabledboolean

Whether the component should ignore user interaction.

-
inputRefRef<HTMLInputElement>

A ref to access the hidden input element.

-
nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

true
readOnlyboolean

Whether the user should be unable to select the radio button.

-
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, RadioRootState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
requiredboolean

Whether the user must choose a value before submitting a form.

-
value*any

The unique identifying value of the radio in a group.

-