RadioGroups are used for selecting only 1 item from a list of 2 or more items. If you need multiple selection or have only one option, use Checkbox. If you need to provide a binary on/off choice that takes effect immediately, use Switch.

also known as Single Select, Option Buttons, Radio Inputs

Figma:

Responsive:

Adaptive:

A11y:

Props

Component props
Name
Type
Default
children
Required
React.Node
-

A collection of RadioGroup.RadioButtons representing the available options, as well as any Labels or layout components (Box, Flex, etc.), if needed. Other components such as Checkboxes should not be included. Note that children can be grouped into organizational components if desired.

id
Required
string
-

A unique identifier for this RadioGroup.

legend
Required
string
-

The description of the radio group that tells users what is being asked of them.

direction
"column" | "row"
"column"

Determines the layout of the group. See the direction variant to learn more.

errorMessage
string
-

Adds an error message below the group of radio buttons. See the error variant to learn more.

legendDisplay
"visible" | "hidden"
"visible"

Whether the legend should be visible or not. If hidden, the legend is still available for screen reader users, but does not appear visually. See the legend visibility variant to learn more.

Usage guidelines

When to use
  • In a list, form or table, to present users with multiple, related options where only one option can be selected.
  • When selection doesn’t take immediate effect and requires form submission.
When not to use
  • Situations where users can select multiple options. Use Checkbox instead.
  • When there is only one item to select or deselect. Use Checkbox instead.
  • When a selection takes immediate effect, especially on mobile. Use Switch instead.
  • When it is visually difficult to observe that RadioGroup turns something on or off. Use Switch instead.

Best Practices

Do

Use RadioGroup to select only one option from a list of 2 or more items.

Don't

Use RadioGroup to select multiple items.

Do

Keep labels and legends clear and brief to avoid too many lines of text that are hard to scan and slow the user down. If clarification is needed, use IconButtons with Tooltips or helperText.

Don't

Use lengthy text that truncates and doesn’t offer clear instructions for how to make a selection.

Do

Use RadioGroup when you need a clear answer to a binary question that requires form submission.

Don't

Use a RadioGroup to turn a state on and off with immediate effect on mobile; use Switch instead.

Accessibility

Labels

Each RadioButton in a RadioGroup should have a label that can be read by screen readers, and that can be clicked or tapped to make it easier for users to select and deselect options. Therefore, make sure to supply the label prop. If that’s not possible, make sure your standalone Label has an htmlFor prop that matches the id of the RadioButton. Test that a RadioButton and label are properly connected by clicking or tapping on the label and confirming that it activates the RadioButton next to it.

Legends

Each RadioGroup should have a legend that clearly delineates what is being chosen. If you cannot use the provided legend styling, legendDisplay can be set to hidden, and an alternative legend can be displayed. See the legend visibility variant for an example.

Keyboard interaction

After focus has been set on the first RadioButton inside a RadioGroup, the arrow keys are used to cycle focus between the various options. Clicking or tapping the label of RadioButton should also focus that particular RadioButton. All RadioGroup.RadioButtons within a RadioGroup should share the same name to ensure keyboard accessibility, but that name needs to be unique from other RadioGroup buttons on the page.

Localization

Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.

Be mindful of label length so that it doesn’t truncate in languages with lengthier character counts.

Subcomponents

RadioGroup.RadioButton

Use RadioGroup.RadioButtons to present an option for selection to the user within a RadioGroup. They should not be used outside of RadioGroup or when the user can select more than one option from a list.

RadioGroup.RadioButton Props

RadioGroup.RadioButton subcomponent props
Name
Type
Default
id
Required
string
-

A unique identifier for the input.

onChange
Required
({
  event: SyntheticInputEvent<HTMLInputElement>,
  checked: boolean,
}) => void
-

Callback triggered when the user interacts with the input.

value
Required
string
-

The value of the input.

badge
{
  text: string,
  type?:
    | "info"
    | "error"
    | "warning"
    | "success"
    | "neutral"
    | "recommendation"
    | "darkWash"
    | "lightWash",
}
-

An optional Badge component can be supplied to add a badge to each radio button. See the badges example for more details.

checked
boolean
false

Indicates if the input is checked. See the state example for more details.

disabled
boolean
false

Indicates if the input is disabled. See the state example for more details.

helperText
string
-

Optional description for the input, used to provide more detail about an option. See the helperText example for more details.

image
React.Node
-

An optional Image component can be supplied to add an image to each radio button. Spacing is already accounted for — simply specify the width and height. See the images example for more details.

label
string
-

The displayed label for the input.

name
string
-

The name given for all radio buttons in a single group.

ref
HTMLInputElement
-

Ref forwarded to the underlying input element. See ref example for more details.

size
"sm" | "md"
"md"

sm: 16px, md: 24px

Variants

Direction

RadioGroups can be shown in a column or row by specifying the direction property.

import { useState } from 'react';
import { Box, Flex, RadioGroup } from 'gestalt';

export default function RadioButtonExample() {
  const [favorite, setFavorite] = useState('');
  const [favoriteFood, setFavoriteFood] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Flex gap={8} wrap>
        <RadioGroup id="directionExample-1" legend="What is your favorite pet?">
          <RadioGroup.RadioButton
            checked={favorite === 'dogs'}
            id="favoriteDog"
            label="Dogs"
            name="favorite"
            onChange={() => setFavorite('dogs')}
            value="dogs"
          />
          <RadioGroup.RadioButton
            checked={favorite === 'cats'}
            id="favoriteCat"
            label="Cats"
            name="favorite"
            onChange={() => setFavorite('cats')}
            value="cats"
          />
          <RadioGroup.RadioButton
            checked={favorite === 'plants'}
            id="favoritePlants"
            label="Plants"
            name="favorite"
            onChange={() => setFavorite('plants')}
            value="plants"
          />
        </RadioGroup>

        <RadioGroup
          direction="row"
          errorMessage="Please select one"
          id="directionExample"
          legend="What is your favorite snack?"
        >
          <RadioGroup.RadioButton
            checked={favoriteFood === 'pizza'}
            id="favoritePizza"
            label="Pizza"
            name="favoriteFood"
            onChange={() => setFavoriteFood('pizza')}
            value="pizza"
          />
          <RadioGroup.RadioButton
            checked={favoriteFood === 'curry'}
            id="favoriteCurry"
            label="Curry"
            name="favoriteFood"
            onChange={() => setFavoriteFood('curry')}
            value="curry"
          />
          <RadioGroup.RadioButton
            checked={favoriteFood === 'sushi'}
            id="favoriteSushi"
            label="Sushi"
            name="favoriteFood"
            onChange={() => setFavoriteFood('sushi')}
            value="sushi"
          />
        </RadioGroup>
      </Flex>
    </Box>
  );
}

Size

RadioButtons can be either sm (16px) or md (24px), which is the default.

import { useState } from 'react';
import { Box, Flex, RadioGroup } from 'gestalt';

export default function RadioButtonExample() {
  const [favorite, setFavorite] = useState('');
  const [favoriteFood, setFavoriteFood] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Flex gap={{ column: 0, row: 8 }}>
        <RadioGroup
          errorMessage="Please select one"
          id="sizeExample"
          legend="What is your favorite snack?"
        >
          <RadioGroup.RadioButton
            checked={favorite === 'pizza'}
            id="favoriteSizePizzaSm"
            label="Pizza"
            name="favoriteFoodSm"
            onChange={() => setFavorite('pizza')}
            size="sm"
            value="pizza"
          />
          <RadioGroup.RadioButton
            checked={favorite === 'curry'}
            id="favoriteSizeCurrySm"
            label="Curry"
            name="favoriteFoodSm"
            onChange={() => setFavorite('curry')}
            size="sm"
            value="curry"
          />
          <RadioGroup.RadioButton
            checked={favorite === 'sushi'}
            id="favoriteSizeSushiSm"
            label="Sushi"
            name="favoriteFoodSm"
            onChange={() => setFavorite('sushi')}
            size="sm"
            value="sushi"
          />
        </RadioGroup>

        <RadioGroup
          errorMessage="Please select one"
          id="sizeExampleMd"
          legend="What is your favorite snack?"
        >
          <RadioGroup.RadioButton
            checked={favoriteFood === 'pizza'}
            id="favoriteSizePizza"
            label="Pizza"
            name="favoriteFood-size"
            onChange={() => setFavoriteFood('pizza')}
            value="pizza"
          />
          <RadioGroup.RadioButton
            checked={favoriteFood === 'curry'}
            id="favoriteSizeCurry"
            label="Curry"
            name="favoriteFood-size"
            onChange={() => setFavoriteFood('curry')}
            value="curry"
          />
          <RadioGroup.RadioButton
            checked={favoriteFood === 'sushi'}
            id="favoriteSizeSushi"
            label="Sushi"
            name="favoriteFood-size"
            onChange={() => setFavoriteFood('sushi')}
            value="sushi"
          />
        </RadioGroup>
      </Flex>
    </Box>
  );
}

States

Disabled RadioButtons cannot be accessed by the keyboard and therefore should not contain any necessary info to complete the choice presented.

import { useState } from 'react';
import { Box, RadioGroup } from 'gestalt';

export default function RadioButtonExample() {
  const [state, setState] = useState('checked');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup id="rowExample" legend="Which state is your favorite?">
        <RadioGroup.RadioButton
          checked={false}
          id="unchecked"
          label="Unchecked"
          name="stateExample"
          onChange={() => setState('unchecked')}
          value="unchecked"
        />
        <RadioGroup.RadioButton
          checked={state === 'checked'}
          id="checked"
          label="Checked"
          name="stateExample"
          onChange={() => setState('checked')}
          value="checked"
        />
        <RadioGroup.RadioButton
          checked={false}
          disabled
          id="uncheckedDisabled"
          label="Unchecked and disabled"
          name="stateExample"
          onChange={() => setState('uncheckedDisabled')}
          value="uncheckedDisabled"
        />
        <RadioGroup.RadioButton
          checked
          disabled
          id="checkedDisabled"
          label="Checked and disabled"
          name="stateExample"
          onChange={() => setState('checkedDisabled')}
          value="checkedDisabled"
        />
      </RadioGroup>
    </Box>
  );
}

With helperText

Use helperText to provide extra context or information for each option.

import { useState } from 'react';
import { Box, RadioGroup } from 'gestalt';

export default function RadioButtonExample() {
  const [availability, setAvailability] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup
        id="helperTextExample"
        legend="Which time slot works best for you?"
      >
        <RadioGroup.RadioButton
          checked={availability === 'monday'}
          helperText="Morning and afternoon"
          id="monday"
          label="Monday"
          name="Availability"
          onChange={() => setAvailability('monday')}
          value="monday"
        />
        <RadioGroup.RadioButton
          checked={availability === 'tuesday'}
          helperText="Morning, afternoon, and evening"
          id="tuesday"
          label="Tuesday"
          name="Availability"
          onChange={() => setAvailability('tuesday')}
          value="tuesday"
        />
        <RadioGroup.RadioButton
          checked={availability === 'wednesday'}
          helperText="Evening only"
          id="Wednesday"
          label="Wednesday"
          name="Availability"
          onChange={() => setAvailability('wednesday')}
          value="wednesday"
        />
      </RadioGroup>
    </Box>
  );
}

With Image

When including images, you can use the helperText property to clearly describe the information being presented by the image, or use the image's alt text to provide more context.

import { useState } from 'react';
import { Box, Image, RadioGroup } from 'gestalt';

export default function RadioButtonExample() {
  const [artPreference, setArtPreference] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup id="imageExample" legend="Pick a placeholder image">
        <RadioGroup.RadioButton
          checked={artPreference === 'coral'}
          helperText="Botanical art in coral and green"
          id="coral"
          image={
            <Box height={100} width={80}>
              <Image
                alt="Botanical art in coral and green"
                fit="cover"
                naturalHeight={1}
                naturalWidth={1}
                src="https://i.ibb.co/7bQQYkX/stock2.jpg"
              />
            </Box>
          }
          label="Coral"
          name="Art Preference"
          onChange={() => setArtPreference('coral')}
          value="coral"
        />
        <RadioGroup.RadioButton
          checked={artPreference === 'blue'}
          helperText="Typography and shoe in blue"
          id="blue"
          image={
            <Box height={100} width={80}>
              <Image
                alt="Typography and shoe in blue"
                fit="cover"
                naturalHeight={1}
                naturalWidth={1}
                src="https://i.ibb.co/jVR29XV/stock5.jpg"
              />
            </Box>
          }
          label="Blue"
          name="Art Preference"
          onChange={() => setArtPreference('blue')}
          value="blue"
        />
        <RadioGroup.RadioButton
          checked={artPreference === 'green'}
          helperText="Abstract art in green"
          id="green"
          image={
            <Box height={100} width={80}>
              <Image
                alt="Abstract art in green"
                fit="cover"
                naturalHeight={1}
                naturalWidth={1}
                src="https://i.ibb.co/FY2MKr5/stock6.jpg"
              />
            </Box>
          }
          label="Green"
          name="Art Preference"
          onChange={() => setArtPreference('green')}
          value="green"
        />
      </RadioGroup>
    </Box>
  );
}

With an error

Use errorMessage to show an error message below the radio options.

import { useState } from 'react';
import { Box, RadioGroup } from 'gestalt';

export default function RadioButtonExample() {
  const [availability, setAvailability] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup
        errorMessage="Please select one"
        id="VariantWithErrorMessage"
        legend="Which time slot works best for you?"
      >
        <RadioGroup.RadioButton
          checked={availability === 'monday'}
          helperText="Morning and afternoon"
          id="mondayError"
          label="Monday"
          name="Availability with error"
          onChange={() => setAvailability('monday')}
          value="monday"
        />
        <RadioGroup.RadioButton
          checked={availability === 'tuesday'}
          helperText="Morning, afternoon, and evening"
          id="tuesdayError"
          label="Tuesday"
          name="Availability with error"
          onChange={() => setAvailability('tuesday')}
          value="tuesday"
        />
        <RadioGroup.RadioButton
          checked={availability === 'wednesday'}
          helperText="Evening only"
          id="WednesdayError"
          label="Wednesday"
          name="Availability with error"
          onChange={() => setAvailability('wednesday')}
          value="wednesday"
        />
      </RadioGroup>
    </Box>
  );
}

With custom labels

The label on RadioGroup.RadioButton can be replaced with a custom Label, as demonstrated below. Ensure the htmlFor property matches the id on the RadioButton.

import { useState } from 'react';
import { Box, Flex, IconButton, Label, RadioGroup, Text } from 'gestalt';

export default function RadioButtonExample() {
  const [favorite, setFavorite] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup id="bestPracticeBudget" legend="Campaign budget">
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <RadioGroup.RadioButton
            checked={favorite === 'daily'}
            id="daily-label-ex-custom"
            name="budget-custom-label"
            onChange={() => setFavorite('daily')}
            value="daily"
          />
          <Label htmlFor="daily-label-ex-custom">
            <Flex alignItems="center">
              <Text>Daily</Text>
              <IconButton
                accessibilityLabel="info"
                icon="info-circle"
                iconColor="gray"
                size="sm"
                tooltip={{
                  text: 'Sets a cap for the amount your campaign can spend each day',
                }}
              />
            </Flex>
          </Label>
        </Flex>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <RadioGroup.RadioButton
            checked={favorite === 'lifetime'}
            id="lifetime-label-ex-custom"
            name="budget-custom-label"
            onChange={() => setFavorite('lifetime')}
            value="lifetime"
          />
          <Label htmlFor="lifetime-label-ex-custom">
            <Flex alignItems="center">
              <Text>Lifetime</Text>
              <IconButton
                accessibilityLabel="info"
                icon="info-circle"
                iconColor="gray"
                size="sm"
                tooltip={{
                  text: 'Sets a cap for the amount your campaign can spend over the course of its lifetime',
                }}
              />
            </Flex>
          </Label>
        </Flex>
      </RadioGroup>
    </Box>
  );
}

Legend visibility

By default, the legend is visible above the items in the RadioGroup. However, if the form items are labeled by content elsewhere on the page, or a more complex legend is needed, the legendDisplay prop can be used to visually hide the legend. In this case, it is still available to screen reader users, but will not appear visually on the screen.

In the example below, the "Primary company account goal" text is acting as a heading and a legend for the radio buttons, so instead of repeating another legend, we visually hide the RadioGroup legend. When a user focuses on the first radio, a screen reader will announce "Sell more products, radio button, 1 of 3, Primary company account goal, group".

import { useState } from 'react';
import { Box, Flex, Heading, Link, RadioGroup, Text } from 'gestalt';

export default function RadioButtonExample() {
  const [goal, setGoal] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Flex direction="column" gap={{ column: 4, row: 0 }}>
        <Flex direction="column" gap={{ column: 2, row: 0 }}>
          <Heading size="400">Primary company account goal</Heading>
          <Text size="200">
            Choose your primary goal for this account to help us better
            understand your needs
            <Text inline size="200" weight="bold">
              <Link
                display="inline"
                href="https://www.pinterest.com/"
                target="blank"
              >
                Additional information
              </Link>
            </Text>
          </Text>
        </Flex>
        <RadioGroup
          id="legendExample"
          legend="Primary company account goal"
          legendDisplay="hidden"
        >
          <RadioGroup.RadioButton
            checked={goal === 'sell'}
            id="sell"
            label="Sell more products"
            name="account goals"
            onChange={() => setGoal('sell')}
            value="sell"
          />
          <RadioGroup.RadioButton
            checked={goal === 'leads'}
            id="leads"
            label="Generate more leads for the company"
            name="account goals"
            onChange={() => setGoal('leads')}
            value="leads"
          />
          <RadioGroup.RadioButton
            checked={goal === 'interest'}
            id="interest"
            label="Create content on Pinterest to attract an audience"
            name="account goals"
            onChange={() => setGoal('interest')}
            value="interest"
          />
        </RadioGroup>
      </Flex>
    </Box>
  );
}

Adding a Popover

RadioButton with an anchor ref to a Popover component doesn't pass the correct positioning to the Popover. Instead set the anchor ref to the parent container.

import { useRef, useState } from 'react';
import { Box, Layer, Link, Popover, RadioGroup, Text } from 'gestalt';

export default function RadioButtonPopoverExample() {
  const [open, setOpen] = useState(false);
  const [option, setOption] = useState('');
  const anchorCatRef = useRef(null);
  const anchorDogRef = useRef(null);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup id="popoverExample" legend="Tell us about yourself">
        <Box ref={anchorCatRef} display="inlineBlock">
          <RadioGroup.RadioButton
            checked={option === 'cat'}
            id="cat"
            label="I'm a cat person"
            onChange={() => {
              setOpen(true);
              setOption('cat');
            }}
            value="cat"
          />
        </Box>
        <Box ref={anchorDogRef} display="inlineBlock">
          <RadioGroup.RadioButton
            checked={option === 'dog'}
            id="dog"
            label="I'm a dog person"
            onChange={() => {
              setOpen(true);
              setOption('dog');
            }}
            value="dog"
          />
        </Box>
        {open && (
          <Layer>
            <Popover
              anchor={
                option === 'cat' ? anchorCatRef.current : anchorDogRef.current
              }
              idealDirection="right"
              onDismiss={() => setOpen(false)}
              positionRelativeToAnchor={false}
              shouldFocus={false}
              size="md"
            >
              <Box padding={3}>
                <Text color="default">
                  <Link
                    href={
                      option === 'cat'
                        ? 'https://www.pinterest.com/search/pins/?q=cats'
                        : 'https://www.pinterest.com/search/pins/?q=dogs'
                    }
                    target="blank"
                    underline="always"
                  >
                    {option === 'cat'
                      ? 'Check out cats on Pinterest!'
                      : 'Check out dogs on Pinterest!'}
                  </Link>
                </Text>
              </Box>
            </Popover>
          </Layer>
        )}
      </RadioGroup>
    </Box>
  );
}

With Badge

The badge prop can be used to add a badge to the RadioButton.

import { useState } from 'react';
import { Box, RadioGroup } from 'gestalt';

export default function RadioButtonBadgeExample() {
  const [option, setOption] = useState('');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <RadioGroup id="badgeExample" legend="What badge would you like?">
        <Box display="inlineBlock">
          <RadioGroup.RadioButton
            badge={{ text: 'badge', type: 'success' }}
            checked={option === 'success'}
            id="success"
            label="I'd like a success badge"
            onChange={() => {
              setOption('success');
            }}
            value="success"
          />
        </Box>
        <Box display="inlineBlock">
          <RadioGroup.RadioButton
            badge={{ text: 'badge', type: 'info' }}
            checked={option === 'info'}
            id="info"
            label="I'd like a info badge"
            onChange={() => {
              setOption('info');
            }}
            value="info"
          />
        </Box>
        <Box display="inlineBlock">
          <RadioGroup.RadioButton
            badge={{ text: 'badge', type: 'warning' }}
            checked={option === 'warning'}
            id="warning"
            label="I'd like a warning badge"
            onChange={() => {
              setOption('warning');
            }}
            value="warning"
          />
        </Box>
        <Box display="inlineBlock">
          <RadioGroup.RadioButton
            badge={{ text: 'badge', type: 'neutral' }}
            checked={option === 'neutral'}
            id="neutral"
            label="I'd like a neutral badge"
            onChange={() => {
              setOption('neutral');
            }}
            value="neutral"
          />
        </Box>
      </RadioGroup>
    </Box>
  );
}

Writing

Do
  • Be brief with RadioGroup button labels so they are easily scanned.
  • Error messages should be simple, clear and direct without negative, overly clever and technical language.
  • A good error message: “To continue you must select one item from this list.”
Don't
  • Include lengthy text labels that make it hard for a user to scan a list of choices.
  • Write error messages that are overly-technical, long, negative, and too clever.
  • A not-so-great error message: “Hey there, nice try, but not selecting something is baaaad. Bad as in bad. Per error code i-five, you must select a choice from this boolean”.

CheckBox
Use when presenting a user with a list of choices where multiple options can be selected.

Switch
Use for single-cell options that can be turned on or off. Examples include a list of settings that take effect immediately without having to confirm form submission.

Fieldset
Fieldset is used under the hood of RadioGroup to ensure accessible groups of radio buttons.