ButtonToggle is a larger alternative to selection components such as Checkbox, RadioButton, and Switch. It enables users to choose between two states - selected or unselected.

also known as Toggle, Action Button, Toggle Switch

Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
selected
Required
boolean
-

Toggles between selected/unselected.

text
Required
string
-

Text to render inside the ButtonToggle to convey the function and purpose of the ButtonToggle.

accessibilityControls
string
-

Specifies the id of an associated element (or elements) whose contents or visibility are controlled by ButtonToggle so that screen reader users can identify the relationship between elements. See the Accessibility guidelines for details on proper usage.

accessibilityExpanded
boolean
-

Indicates that ButtonToggle hides or exposes collapsible components and expose whether they are currently expanded or collapsed. See the Accessibility guidelines for details on proper usage.

accessibilityLabel
string
-

Label for screen readers to announce ButtonToggle. See the Accessibility guidelines for details on proper usage.

color
"red" | "transparent" | readonly [SkinColor, SkinColor, SkinColor, SkinColor]
"transparent"

The background color of ButtonToggle.

This prop also accepts an array of 4 skin tones (skinTone1, skinTone2, ..., skinTone16) to create a color picker. See the Color Picker Variant for details on proper usage.

dataTestId
string
-

Available for testing purposes, if needed. Consider better queries before using this prop.

disabled
boolean
false

Indicates if ButtonToggle is disabled. Disabled ButtonToggles are inactive and cannot be interacted with. See the state variant for details on proper usage.

graphicSrc
string
-

An icon displayed above the text to illustrate the meaning of the option selected by the ButtonToggle.

hasDropdown
boolean
-

Indicates that a component controls the appearance of interactive popup elements, such as menu or dialog. See the Accessibility guidelines for details on proper usage.

iconStart
unknown
-

An icon displayed before the text to help clarify the usage of ButtonToggle.

onBlur
(arg1: { event: React.FocusEvent<HTMLButtonElement> }) => void
-

Callback invoked when ButtonToggle loses focus.

onClick
(arg1: {
  event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>;
}) => void
-

Callback invoked when the user clicks (press and release) on ButtonToggle with the mouse or keyboard.

onFocus
(arg1: { event: React.FocusEvent<HTMLButtonElement> }) => void
-

Callback invoked when ButtonToggle gains focus.

size
"sm" | "md" | "lg"
"md"

sm: 32px, md: 40px, lg: 48px

See the size variant variant to learn more.

Usage guidelines

When to use
  • The ButtonToggle should be used when you require a binary component with distinct on/off states
  • To demonstrate that items are actively selected for filtering
When not to use
  • Avoid using the ButtonToggle when a simpler Checkbox, RadioButton, or Switch can be used instead.
  • Do not use ButtonToggle in replacement of a Button, it should only be used for selected and unselected functionality

Best practices

Do

To make it clearer, you may want to change the label text to indicate that the ButtonToggle has been selected. For instance, changing "Follow" to "Following."

Do

Make sure that the ButtonToggle(s) in your application are consistently styled and placed. This should also apply to their sizing, maintaining uniformity throughout the experience.

Do

Place the ButtonToggle(s) in a location where users would naturally expect to find them, taking into consideration the context. For instance, position it next to a related feature.

Do

If the ButtonToggle(s) trigger a significant action or irreversible change, it is recommended to include a confirmation, such as a ModalAlert message.

Accessibility

ARIA attributes

When ButtonToggle text does not provide sufficient context about the ButtonToggle’s behavior, supply a short, descriptive label for screen-readers using accessibilityLabel.

Texts like "Follow/ing" can be confusing when a screen reader reads them out of context. In those cases, we must pass an alternative text with deeper context to replace the ButtonToggle text, like “Follow/ing Ryan”.

  • accessibilityLabel: if present, read by screen readers read instead of the text prop. It populates aria-label.

If ButtonToggle is used as a control button to show/hide content, we recommend passing the following ARIA attributes to assist screen readers:

  • accessibilityControls: informs the screen reader that ButtonToggle controls the display of an interactive widget or element, or is used to modify another component. It can be used to associate the corresponding element with the ButtonToggle. It populates aria-controls.

In the Color Picker variant, the ButtonToggle does not display the text given as a visible label, but the text prop is still used as a fallback accessiblity label, and is still required.

Color contrast in disabled state

Disabled ButtonToggles do not need to pass color contrast guidelines.

From w3.org, 1.4.3 Contrast (Minimum): Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement.

Our current disabled ButtonToggle implementation does fail to pass color contrast on accessibility integration tests. To exclude disabled buttontoggles from the integration tests we recomment conditionally setting a data-test-id={ isDisabled ? "disabled-button-<name>" : undefined } and excluding them from the integration test.

On cypress-axe that can be achieved with cy.a11yCheck({ exclude: [['[data-test-id="disabled-button-submit"]']] })

Localization

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

Avoid truncating ButtonToggle text whenever possible. Refer to the ButtonToggle usage guidelines for more information

import { useState } from 'react';
import { ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  const [selected, setSelected] = useState(false);

  return (
    <Flex
      alignItems="center"
      gap={2}
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonToggle
        iconStart="sparkle"
        onClick={() => setSelected((value) => !value)}
        selected={selected}
        size="lg"
        text={selected ? 'Gefolgt' : 'Folgen'}
      />
    </Flex>
  );
}

Variants

States

  1. Unselected
    The initial state of a ButtonToggle that represents it is in a non-selected state.

  2. Disabled
    Used to block user interaction such as hover, focus and click. Disabled ButtonToggles are completely unreachable by a keyboard and screenreader, so do not attach Tooltips to disabled ButtonToggles.

  3. Selected
    When ButtonToggle is currently active or selected.

Unselected
import { ButtonGroup, ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonGroup>
        <ButtonToggle selected={false} size="lg" text="Follow" />
        <ButtonToggle color="red" selected={false} size="lg" text="Save" />
        <ButtonToggle
          graphicSrc="https://s.pinimg.com/webapp/protective-8fad3fab.svg"
          selected={false}
          size="lg"
          text="Protective"
        />
        <ButtonToggle
          color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
          selected={false}
          size="lg"
          text="Fair Skin"
        />
      </ButtonGroup>
    </Flex>
  );
}

Selected
import { ButtonGroup, ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonGroup>
        <ButtonToggle selected size="lg" text="Following" />
        <ButtonToggle color="red" selected size="lg" text="Saved" />
        <ButtonToggle
          graphicSrc="https://s.pinimg.com/webapp/protective-8fad3fab.svg"
          selected
          size="lg"
          text="Protective"
        />
        <ButtonToggle
          color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
          selected
          size="lg"
          text="Fair Skin"
        />
      </ButtonGroup>
    </Flex>
  );
}

Disabled
import { ButtonGroup, ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      gap={1}
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonGroup>
        <ButtonToggle disabled selected={false} size="lg" text="Follow" />
        <ButtonToggle disabled selected size="lg" text="Following" />
      </ButtonGroup>
      <ButtonGroup>
        <ButtonToggle
          color="red"
          disabled
          selected={false}
          size="lg"
          text="Save"
        />
        <ButtonToggle color="red" disabled selected size="lg" text="Saved" />
      </ButtonGroup>
      <ButtonGroup>
        <ButtonToggle
          disabled
          graphicSrc="https://s.pinimg.com/webapp/protective-8fad3fab.svg"
          selected={false}
          size="lg"
          text="Protective"
        />
        <ButtonToggle
          disabled
          graphicSrc="https://s.pinimg.com/webapp/protective-8fad3fab.svg"
          selected
          size="lg"
          text="Protective"
        />
      </ButtonGroup>
      <ButtonGroup>
        <ButtonToggle
          color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
          disabled
          selected={false}
          size="lg"
          text="Fair Skin"
        />
        <ButtonToggle
          color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
          disabled
          selected
          size="lg"
          text="Fair Skin"
        />
      </ButtonGroup>
    </Flex>
  );
}

Size

ButtonToggle is available in 3 fixed sizes. The ButtonToggle text has always a fixed size of 16px:

  1. lg (48px)
    Large is the only size that should be used on Pinner surfaces.
  2. md (40px)
    Medium is used on more dense UI such as business surfaces or internal tools.
  3. sm (32px)
    Small should be used sparingly and only in places where the UI is very dense.
size="sm"
size="md"
size="lg"

Color

  1. Red (Primary)
    High emphasis, used for the Save action.
  2. Transparent
    Low emphasis when placed on dark/image backgrounds, used for actions in that context.
import { ButtonToggle, Flex, Text } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      gap={6}
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Flex direction="column" gap={2}>
        <ButtonToggle color="red" selected={false} size="lg" text="Save" />
        <Text size="200" weight="bold">
          color=&quot;red&quot;
        </Text>
      </Flex>
      <Flex direction="column" gap={2}>
        <ButtonToggle
          color="transparent"
          selected={false}
          size="lg"
          text="Follow"
        />
        <Text size="200" weight="bold">
          color=&quot;transparent&quot;
        </Text>
      </Flex>
    </Flex>
  );
}

Thumbnail

The graphicSrc prop adds a thumbnail above the ButtonToggle text, whose source is the URL provided in the prop.

This variant also changes the shape of the ButtonToggle.

import { useState } from 'react';
import { ButtonGroup, ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  const [selected, setSelected] = useState(false);

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonGroup>
        <ButtonToggle
          graphicSrc="https://s.pinimg.com/webapp/protective-8fad3fab.svg"
          onClick={() => setSelected((value) => !value)}
          selected={selected}
          size="lg"
          text="Protective"
        />
        <ButtonToggle
          disabled
          graphicSrc="https://s.pinimg.com/webapp/protective-8fad3fab.svg"
          onClick={() => setSelected((value) => !value)}
          selected={false}
          size="lg"
          text="Disabled"
        />
      </ButtonGroup>
    </Flex>
  );
}

The ButtonToggle can be used as a dropdown trigger by setting the hasDropdown prop to true.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  ButtonToggle,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const anchorRef = useRef(null);

  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Box display="flex" justifyContent="center" margin={2} width="100%">
        <ButtonToggle
          ref={anchorRef}
          accessibilityControls="demo-dropdown-example"
          accessibilityExpanded={open}
          hasDropdown
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          size="lg"
          text="Menu"
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="demo-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Item
            onSelect={onSelect}
            option={{ value: 'Download image', label: 'Download image' }}
            selected={selected}
          />
          <Dropdown.Item
            badge={{ text: 'New' }}
            onSelect={onSelect}
            option={{ value: 'Hide Pin', label: 'Hide Pin' }}
            selected={selected}
          />
          <Dropdown.Link
            href="https://pinterest.com"
            iconEnd="visit"
            onClick={({ event }) => event.preventDefault()}
            option={{ value: 'Report Pin', label: 'Report Pin' }}
          />
          <Dropdown.Item
            onSelect={onSelect}
            option={{ value: 'Delete Pin', label: 'Delete Pin' }}
            selected={selected}
          />
        </Dropdown>
      )}
    </Fragment>
  );
}

Color Picker

If the color prop is an array of 4 skin tones, ButtonToggle is converted into a textless color picker.
The text prop is still used as a fallback accessiblity label, and is still required.

The colors in the array are displayed in a 4-quadrant pattern across the ButtonToggle.
The skin tones currently supported are:

skinTone1 #F0E3DC skinTone2 #F8D7D8 skinTone3 #F2D7BE skinTone4 #F7C3AF skinTone5 #DEBAB0 skinTone6 #E0999A skinTone7 #DDA67C skinTone8 #D98A64
skinTone9 #9A6B52 skinTone10 #A25847 skinTone11 #B37143 skinTone12 #BF6951 skinTone13 #683929 skinTone14 #34261F skinTone15 #64281B skinTone16 #4F2221
import { useState } from 'react';
import { Box, ButtonGroup, ButtonToggle, Flex, Text } from 'gestalt';

export default function Example() {
  const [selected, setSelected] = useState(false);

  return (
    <Flex
      alignItems="center"
      direction="column"
      gap={2}
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonToggle
        color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
        onClick={() => setSelected((value) => !value)}
        selected={selected}
        size="lg"
        text="Fair Skin"
      />
      <Text size="200" weight="bold">
        {`color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}`}
      </Text>
      <Box padding={4} />
      <ButtonGroup>
        <ButtonToggle
          color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
          disabled
          onClick={() => setSelected((value) => !value)}
          selected={false}
          size="lg"
          text="Fair Skin"
        />
        <ButtonToggle
          color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']}
          disabled
          onClick={() => setSelected((value) => !value)}
          selected
          size="lg"
          text="Fair Skin"
        />
      </ButtonGroup>
      <Text size="200" weight="bold">
        {`color={['skinTone1', 'skinTone2', 'skinTone3', 'skinTone4']} disabled`}
      </Text>
    </Flex>
  );
}

Icons

iconStart adds an icon before the ButtonToggle text.

Text can be ommited when using an icon to have an icon-only ButtonToggle, but in that case, accessibilityLabel is required. ButtonToggle will fail to render if both text and accessibilityLabel are unset.

import { ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonToggle
        iconStart="sparkle"
        selected={false}
        size="lg"
        text="Follow"
      />
    </Flex>
  );
}

import { ButtonToggle, Flex } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      gap={2}
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <ButtonToggle
        accessibilityLabel="sparkle"
        iconStart="sparkle"
        selected={false}
        size="lg"
        text=""
      />
      <ButtonToggle
        accessibilityLabel="sparkle"
        hasDropdown
        iconStart="sparkle"
        selected={false}
        size="lg"
        text=""
      />
    </Flex>
  );
}

Writing

Do
  • Use fewer than three words, ideally only one.
  • Use clear and concise copy for labels, tooltips, and any supporting text.
  • Make sure that all text is easy to translate for localization purposes.
Don't
  • Do not use punctuation.

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Ready
Component is available in Figma for web and mobile web.
Responsive Web
Component does not respond to changing viewport sizes in web and mobile web.

RadioGroup
Use when presenting a user with a list of choices for which there can only be one selection.

Checkbox
Used when presenting a user with a list of choices for which there can be multiple selections.

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.

TagData
TagData enables people to select multiple categories to compare with each other in a graph or chart.

TileData
TileData enables users to select multiple categories to compare with each other in a graph or chart view, while still being able to see all of the data points.