ButtonToggle
Props
Usage guidelines
- 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
- 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
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."
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.
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.
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 thetext
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
Unselected
The initial state of a ButtonToggle that represents it is in a non-selected state.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.Selected
When ButtonToggle is currently active or selected.
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> ); }
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> ); }
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:
lg
(48px)
Large is the only size that should be used on Pinner surfaces.md
(40px)
Medium is used on more dense UI such as business surfaces or internal tools.sm
(32px)
Small should be used sparingly and only in places where the UI is very dense.
Color
- Red (Primary)
High emphasis, used for the Save action. - 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="red" </Text> </Flex> <Flex direction="column" gap={2}> <ButtonToggle color="transparent" selected={false} size="lg" text="Follow" /> <Text size="200" weight="bold"> color="transparent" </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> ); }
Dropdown
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:
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
- 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.
- Do not use punctuation.
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. |
Related
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.