Thumbprint logo

Components

Radio

Grouped controls for selecting a single value in forms

Migrating from v1

The v2 RadioGroup is built on React Aria and styled with Thumbprint v2 semantic tokens. It replaces the v1 Radio, which required every radio to share a name and manage its own checked state. With v2 you render a single RadioGroup that owns the selection, and one RadioGroup.Radio per option.

Key differences:

  • Selection moves to the group. Replace each radio’s isChecked and onChange(isChecked, id, event) with the group’s value / defaultValue and onChange(value). Each RadioGroup.Radio takes a unique value.
  • No more shared name wiring or page-unique values for keyboard navigation — React Aria handles grouping, roving focus, and arrow-key selection.
  • Per-radio props carry over with the same names: isDisabled, hasError, radioVerticalAlign, labelPadding, id, accessibilityLabel, and dataTestId. isDisabled and hasError can also be set once on the group and cascade to every radio.
  • New capabilities: a type="control" card treatment, a description per radio, and a group orientation.
  • The deprecated dataTest prop is removed (use dataTestId).

Before (v1):

function RadioExample() {
const [selectedId, setSelectedId] = React.useState('long-distance');
return (
<div>
<Radio
id="long-distance"
name="moving"
isChecked={selectedId === 'long-distance'}
onChange={(isChecked, id) => setSelectedId(id)}
>
Long Distance Moving
</Radio>
<Radio
id="furniture"
name="moving"
isChecked={selectedId === 'furniture'}
onChange={(isChecked, id) => setSelectedId(id)}
>
Furniture Moving
</Radio>
</div>
);
}

After (v2):

function RadioExample() {
const [value, setValue] = React.useState('long-distance');
return (
<RadioGroup value={value} onChange={setValue} accessibilityLabel="Moving services">
<RadioGroup.Radio value="long-distance">Long Distance Moving</RadioGroup.Radio>
<RadioGroup.Radio value="furniture">Furniture Moving</RadioGroup.Radio>
</RadioGroup>
);
}

Basic radio group

Provide value and onChange for a controlled group. Each RadioGroup.Radio reports its value when selected, and only one radio in the group can be selected at a time.

function RadioExample() {
    const [value, setValue] = React.useState('long-distance');

    return (
        <RadioGroup value={value} onChange={setValue} accessibilityLabel="Moving services">
            <RadioGroup.Radio value="long-distance">Long Distance Moving</RadioGroup.Radio>
            <RadioGroup.Radio value="furniture">Furniture Moving and Heavy Lifting</RadioGroup.Radio>
            <RadioGroup.Radio value="pool-table">Pool Table Moving</RadioGroup.Radio>
        </RadioGroup>
    );
}

Uncontrolled groups

For an uncontrolled group, set an initial selection with defaultValue and let React Aria manage the state internally.

<RadioGroup defaultValue="long-distance" accessibilityLabel="Moving services">
    <RadioGroup.Radio value="long-distance">Long Distance Moving</RadioGroup.Radio>
    <RadioGroup.Radio value="furniture">Furniture Moving and Heavy Lifting</RadioGroup.Radio>
    <RadioGroup.Radio value="pool-table">Pool Table Moving</RadioGroup.Radio>
</RadioGroup>

Disabled radios

The isDisabled prop visually and functionally disables a radio along with its label. Set it on a single RadioGroup.Radio to disable one option, or on the RadioGroup to disable the whole group.

<RadioGroup defaultValue="long-distance" accessibilityLabel="Moving services">
    <RadioGroup.Radio value="long-distance">Long Distance Moving</RadioGroup.Radio>
    <RadioGroup.Radio value="furniture" isDisabled>
        Furniture Moving and Heavy Lifting
    </RadioGroup.Radio>
    <RadioGroup.Radio value="pool-table">Pool Table Moving</RadioGroup.Radio>
</RadioGroup>

Radio group with an error

The hasError prop visually represents an error and marks the group invalid for assistive technologies. Set on the group it cascades to every radio; individual radios can override it. It should be used alongside an error message that helps users advance through the form.

function RadioExample() {
    const [value, setValue] = React.useState(undefined);

    return (
        <RadioGroup value={value} onChange={setValue} hasError accessibilityLabel="Moving services">
            <RadioGroup.Radio value="long-distance">Long Distance Moving</RadioGroup.Radio>
            <RadioGroup.Radio value="furniture">Furniture Moving and Heavy Lifting</RadioGroup.Radio>
        </RadioGroup>
    );
}

Display type

standard (the default) renders the inline, borderless layout. control wraps each radio and its label in a padded, bordered card whose border becomes brand-colored when selected. Set type on the group to apply it to every radio, or on a single RadioGroup.Radio to override.

function RadioExample() {
    const [value, setValue] = React.useState('long-distance');

    return (
        <RadioGroup type="control" value={value} onChange={setValue} accessibilityLabel="Plans">
            <RadioGroup.Radio value="long-distance">Long Distance Moving</RadioGroup.Radio>
            <RadioGroup.Radio value="furniture">Furniture Moving and Heavy Lifting</RadioGroup.Radio>
            <RadioGroup.Radio value="pool-table">Pool Table Moving</RadioGroup.Radio>
        </RadioGroup>
    );
}

Radios with a description

The description prop renders secondary text beneath a radio’s label and emphasizes the label. Radios with a description default to top-aligned indicators; this can be overridden with radioVerticalAlign.

function RadioExample() {
    const [value, setValue] = React.useState('basic');

    return (
        <RadioGroup type="control" value={value} onChange={setValue} accessibilityLabel="Plans">
            <RadioGroup.Radio value="basic" description="Best for individuals getting started.">
                Basic
            </RadioGroup.Radio>
            <RadioGroup.Radio value="pro" description="For growing teams that need more.">
                Pro
            </RadioGroup.Radio>
        </RadioGroup>
    );
}

Vertical alignment

Indicators are vertically centered against single-line labels by default. When a label spans multiple lines, set radioVerticalAlign="top" (on the group or a single radio) to align the indicator with the first line. The prop only has a visible effect when the label is taller than the indicator.

<RadioGroup
    defaultValue="standard"
    radioVerticalAlign="top"
    accessibilityLabel="Shipping speed"
>
    <RadioGroup.Radio value="standard">
        Standard shipping. Arrives in five to seven business days, tracking included.
    </RadioGroup.Radio>
    <RadioGroup.Radio value="express">
        Express shipping. Arrives in one to two business days, signature on delivery.
    </RadioGroup.Radio>
</RadioGroup>

Horizontal orientation

Radios stack vertically by default. Set orientation="horizontal" on the group to lay them out in a row.

<RadioGroup
    defaultValue="yes"
    orientation="horizontal"
    accessibilityLabel="Are you available?"
>
    <RadioGroup.Radio value="yes">Yes</RadioGroup.Radio>
    <RadioGroup.Radio value="no">No</RadioGroup.Radio>
    <RadioGroup.Radio value="maybe">Maybe</RadioGroup.Radio>
</RadioGroup>

Custom label padding

The labelPadding prop expands a radio’s click/touch target by applying a CSS padding string to its label, mirroring the v1 prop. It is most useful with the control type, since it overrides the bordered card’s padding.

<RadioGroup defaultValue="long-distance" accessibilityLabel="Moving services">
    <RadioGroup.Radio value="long-distance" labelPadding="16px 8px">
        Long Distance Moving
    </RadioGroup.Radio>
    <RadioGroup.Radio value="furniture" labelPadding="16px 8px">
        Furniture Moving and Heavy Lifting
    </RadioGroup.Radio>
</RadioGroup>

Multi-column content

It’s possible to provide complex UIs to the children prop. Clicking on the content will select the related radio. Use radioVerticalAlign="top" to align the indicator with the first line of tall content.

function RadioExample() {
    const [value, setValue] = React.useState(undefined);

    return (
        <RadioGroup value={value} onChange={setValue} accessibilityLabel="Pros">
            <RadioGroup.Radio value="austin" radioVerticalAlign="center">
                <div className="flex">
                    <div className="flex-none">
                        <UserAvatar imageUrl="https://randomuser.me/api/portraits/women/63.jpg" />
                    </div>
                    <div className="flex items-center pl4" style={{ flex: '1 0 0%' }}>
                        <div>
                            <span className="b">Austin Entertainment LLC.</span>
                            <p>DJs, photo booths, and photography for all of your event needs.</p>
                        </div>
                        <div className="b ml-auto">$120/hr</div>
                    </div>
                </div>
            </RadioGroup.Radio>
        </RadioGroup>
    );
}

The full props for RadioGroup.Radio and RadioGroup are listed in the tables below.

Props

Radio

An individual option within a `RadioGroup`, rendered as `RadioGroup.Radio`. Exported (as a named export, not via the package's public entry point) so documentation tooling can generate a props table for it; consumers should always use it through `RadioGroup.Radio`.
  • value
    required

    The value submitted (and reported to the group's onChange) when this radio is selected. Must be unique within the group. Maps to React Aria's value.

    Type
    string
  • children

    Text or elements that appear within the label. If children is not provided, use accessibilityLabel to label the radio for assistive technologies.

    Type
    React.ReactNode
  • id

    The id applied to the underlying radio <input> element, e.g. to associate it with an external <label>. Maps to React Aria's id.

    Type
    string
  • description

    Secondary text rendered beneath the label. When provided, the label is emphasized and the indicator aligns to the top. Maps to the [data-has-description] attribute.

    Type
    string
  • isDisabled

    Disables this individual radio. Maps to React Aria's isDisabled.

    Type
    boolean
  • hasError

    Renders this radio in an error state (red indicator and text). Defaults to the group's hasError value when omitted. Maps to the [data-has-error] attribute.

    Type
    boolean
  • type

    Display type for this radio. Defaults to the group's type when omitted. See RadioGroupProps['type'] for details. Maps to the [data-type] attribute.

    Type
    'standard' | 'control'
  • radioVerticalAlign

    Determines how the indicator is vertically aligned relative to children. Defaults to the group's radioVerticalAlign; when neither is set, radios with a description default to 'top' and the rest to 'center'. Maps to the [data-align] attribute.

    Type
    'top' | 'center'
  • labelPadding

    Padding applied to the radio's clickable label, primarily to expand the click/touch target. Accepts a CSS padding string such as 8px or 8px 16px. When omitted, the component uses the standardized Thumbprint v2 spacing. With the 'control' type it overrides the bordered card's padding.

    Type
    string
  • onKeyDown

    Function that is called when the user presses a key while focused on the radio. Maps to React Aria's onKeyDown.

    Type
    AriaRadioFieldProps['onKeyDown']
  • accessibilityLabel

    Accessible label for the radio. Only needed if non-textual children are provided (e.g. images, emojis). Maps to React Aria's aria-label.

    Type
    string
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

RadioGroup

  • children
    required

    The RadioGroup.Radio options that make up the group.

    Type
    React.ReactNode
  • value

    The currently selected value. Provide together with onChange for a controlled group. Maps to React Aria's value.

    Type
    string
  • defaultValue

    The initially selected value for an uncontrolled group. Maps to React Aria's defaultValue.

    Type
    string
  • onChange

    Function that runs when the selected radio changes. It receives the newly selected radio's value. Maps to React Aria's onChange.

    Type
    (value: string) => void
  • name

    The name attribute used when the group is submitted as part of an HTML form. React Aria applies it to the underlying radio inputs.

    Type
    string
  • accessibilityLabel

    Accessible label for the group. Maps to React Aria's aria-label.

    Type
    string
  • isDisabled

    Disables every radio in the group. Maps to React Aria's isDisabled.

    Type
    boolean
  • isRequired

    Adds the required HTML attribute to the group. Maps to React Aria's isRequired.

    Type
    boolean
  • hasError

    Renders the group in an error state. Marks the group invalid for assistive technologies (React Aria's isInvalid) and cascades to each radio so the indicator and text turn red. Individual radios may override this with their own hasError prop.

    Type
    boolean
    Default
    false
  • orientation

    Lays the radios out vertically (default) or horizontally. Maps to React Aria's orientation and the [data-orientation] attribute.

    Type
    'vertical' | 'horizontal'
    Default
    'vertical'
  • type

    Display type for every radio in the group. 'standard' renders the inline, borderless layout (the default). 'control' wraps each radio and its label in a bordered, padded card whose border turns brand-colored when selected. Cascades to each radio, which may override it with its own type prop. Maps to the [data-type] attribute.

    Type
    'standard' | 'control'
    Default
    'standard'
  • radioVerticalAlign

    Determines how each radio's indicator is vertically aligned relative to its label. When unset, radios with a description default to 'top' and the rest to 'center'. 'top' is best for multi-line labels or descriptions; 'center' for single-line labels. Cascades to each radio, which may override it with its own radioVerticalAlign prop. Maps to the [data-align] attribute.

    Type
    'top' | 'center'
  • dataTestId

    A selector hook into the React component for use in automated testing environments. Applied to the group's root element.

    Type
    string
  • rowGap

    Overrides the default vertical space between radios. Accepts a CSS length such as 4px or 12px. Applied as the row gap on the wrapper around the radios.

    Type
    string