also known as Typeahead, Autocomplete, Autosuggest
Props
Usage guidelines
- Presenting users with a long list of options (typically 10 or more) that can be filtered by typing in the text field.
- For shorter lists of items where filtering is not needed, typically under 10 items.
Best practices
Use ComboBox to allow the user to edit or copy the textfield input values to select and/or narrow down from a given list of options.
Use ComboBox for a simple list of items. Use SelectList instead for the added native mobile functionality.
Accessibility
Labels
ComboBox requires both label
and accessibilityClearButtonLabel
. By default, the label
is visible above TextField. However, if the form items are labeled by content elsewhere on the page, or a more complex label is needed, the labelDisplay
prop can be used to visually hide the label. In this case, it is still available to screen reader users, but will not appear visually on the screen.
In the example below, the "Discover this week's top searched trends across all categories" text is acting as a heading, so instead of repeating another label, we visually hide the label. When a user focuses on the ComboBox, a screen reader will announce "Choose a category to display top search trends, Select category".
import { useState } from 'react'; import { Box, ComboBox, Flex, Heading, Link, Text } from 'gestalt'; export default function Example() { const CATEGORIES = [ 'All Categories', 'Food and drinks', 'Beauty', 'Home decor', 'Fashion', 'Travel', 'Art', 'Quotes', 'Entertainment', 'Entertainment', 'DIY and crafts', 'Health', 'Wedding', 'Event planning', 'Gardening', 'Parenting', 'Vehicles', 'Design', 'Sport', 'Electronics', 'Animals', 'Finance', 'Architecture', ]; const options = CATEGORIES.map((category, index) => ({ label: category, value: `value${index}`, })); const [errorMessage, setErrorMessage] = useState(); const handleOnBlur = ({ value }) => { if (value !== '' && !CATEGORIES.includes(value)) setErrorMessage('Please, select a valid option'); }; const resetErrorMessage = errorMessage ? () => setErrorMessage() : () => {}; return ( <Box padding={2}> <Flex direction="column" gap={4}> <Heading size="500"> Discover this week's top searched trends across all categories </Heading> <Text inline> Wanna learn how trends work? <Text inline weight="bold"> {' '} Read{' '} <Link accessibilityLabel="Learn how trends on Pinterest work" display="inlineBlock" href="https://business.pinterest.com/content/pinterest-predicts/" target="blank" > {' '} additional information </Link> </Text> </Text> <Flex justifyContent="center" width="100%"> <Box width={320}> <ComboBox accessibilityClearButtonLabel="Clear category value" errorMessage={errorMessage} id="hiddenLabel" label="Choose a category to display top search trends" labelDisplay="hidden" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={resetErrorMessage} onClear={resetErrorMessage} options={options} placeholder="Select category" /> </Box> </Flex> </Flex> </Box> ); }
Keyboard interaction
- Hitting
Enter
orSpace
key on the ComboBox's trigger opens the options list - Once an item is selected, hitting
Enter
orSpace
on the clear button clears the selection and returns focus to the input textfield Escape
key closes the options list, while moving focus back on the ComboBox's trigger- Arrow keys are used to navigate items within the options list
Enter
key selects an item within the options listTab
orShift + Tab
close the options list and move focus accordingly
Localization
Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.
Note that accessibilityClearButtonLabel
and noResultText
are optional as DefaultLabelProvider provides default strings. Use custom labels if they need to be more specific.
import { useState } from 'react'; import { Box, ComboBox, DefaultLabelProvider } from 'gestalt'; export default function Example() { const PRONOUNS = [ 'Dey/Dem', 'Er/Ihm', 'Ey/Em', 'He/Him', 'Hen/Hens', 'She/Her', 'Sie/Ihr', 'They/Them', 'Xier/Xiem', ]; const options = PRONOUNS.map((pronoun, index) => ({ label: pronoun, value: `value${index}`, })); const [errorMessage, setErrorMessage] = useState(); const resetErrorMessage = errorMessage ? () => setErrorMessage() : () => {}; return ( <DefaultLabelProvider labels={{ ComboBox: { noResultText: 'Keine Ergebnisse', accessibilityClearButtonLabel: 'Eingabe löschen.', }, }} > <Box padding={8} width="100%"> <ComboBox accessibilityClearButtonLabel="Löscht den aktuellen Wert" errorMessage={errorMessage} helperText="Wählen Sie die Pronomen, die in Ihrem Profil erscheinen sollen, damit andere wissen, wie sie Sie ansprechen sollen. Sie können diese jederzeit bearbeiten oder entfernen." id="header" label="Pronomen" onBlur={({ value }) => { if (value !== '' && !PRONOUNS.includes(value)) setErrorMessage('Bitte wählen Sie eine gültige Option.'); }} onChange={resetErrorMessage} onClear={resetErrorMessage} options={options} placeholder="Fügen Sie Ihre Pronomen hinzu" /> </Box> </DefaultLabelProvider> ); }
Variants
Controlled vs Uncontrolled
ComboBox can be used as a controlled or an uncontrolled component. An uncontrolled ComboBox stores its own state internally and updates it based on the user input. On the other side, a controlled ComboBox's state is managed by a parent component. The parent component's state passes new values through props to the controlled component which notifies changes through event callbacks.
An uncontrolled ComboBox should be used for basic cases where no default value or tags are required. Don't pass inputValue
or selectedOptions
props to keep the component uncontrolled. By passing inputValue
to ComboBox, the component fully manages its internal state: any value different from null
and undefined
makes Combobox controlled.
import { useState } from 'react'; import { Box, ComboBox } from 'gestalt'; export default function Example() { const PRONOUNS = [ 'ey / em', 'he / him', 'ne / nem', 'she / her', 'they / them', 've / ver', 'xe / xem', 'xie / xem', 'zie / zem', ]; const options = PRONOUNS.map((pronoun, index) => ({ label: pronoun, value: `value${index}`, })); const [errorMessage, setErrorMessage] = useState(); const handleOnBlur = ({ value }) => { if (value !== '' && !PRONOUNS.includes(value)) setErrorMessage('Please, select a valid option'); }; const resetErrorMessage = errorMessage ? () => setErrorMessage() : () => {}; return ( <Box padding={8} width="100%"> <ComboBox accessibilityClearButtonLabel="Clear the current value" errorMessage={errorMessage} helperText="Choose your pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="uncontrolled" label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={resetErrorMessage} onClear={resetErrorMessage} options={options} placeholder="Add your pronouns" /> </Box> ); }
A controlled ComboBox is required if a selected value is set, as shown in the first example. In the second example, values are set programatically. Controlled Comboboxes with
tags are also controlled components. A controlled ComboBox requires three value props: options
, inputValue
, and selectedOptions
. ComboBox is notified of changes via the onChange
, onSelect
, onBlur
, onFocus
, onKeyDown
, and onClear
props. All values displayed by ComboBox at any time are controlled externally. To clear inputValue
, set the value to an empty string inputValue
= ""
, null
or undefined
values turn ComboBox into an uncontrolled component.
function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } import { useState } from 'react'; import { Box, ComboBox, Flex, Text } from 'gestalt'; const US_STATES = [ 'AK - Alaska', 'AL - Alabama', 'AR - Arkansas', 'AS - American Samoa', 'AZ - Arizona', 'CA - California', 'CO - Colorado', 'CT - Connecticut', 'DC - District of Columbia', 'DE - Delaware', 'FL - Florida', 'GA - Georgia', 'GU - Guam', 'HI - Hawaii', 'IA - Iowa', 'ID - Idaho', 'IL - Illinois', 'IN - Indiana', 'KS - Kansas', 'KY - Kentucky', 'LA - Louisiana', 'MA - Massachusetts', 'MD - Maryland', 'ME - Maine', 'MI - Michigan', 'MN - Minnesota', 'MO - Missouri', 'MS - Mississippi', 'MT - Montana', 'NC - North Carolina', 'ND - North Dakota', 'NE - Nebraska', 'NH - New Hampshire', 'NJ - New Jersey', 'NM - New Mexico', 'NV - Nevada', 'NY - New York', 'OH - Ohio', 'OK - Oklahoma', 'OR - Oregon', 'PA - Pennsylvania', 'PR - Puerto Rico', 'RI - Rhode Island', 'SC - South Carolina', 'SD - South Dakota', 'TN - Tennessee', 'TX - Texas', 'UT - Utah', 'VA - Virginia', 'VI - Virgin Islands', 'VT - Vermont', 'WA - Washington', 'WI - Wisconsin', 'WV - West Virginia', 'WY - Wyoming', ]; export default function Example() { const usStatesOptions = US_STATES.map((pronoun, index) => ({ label: pronoun, value: `value${index}`, })); const [suggestedOptions, setSuggestedOptions] = useState(usStatesOptions); const [inputValue, setInputValue] = useState( _nullishCoalesce( _optionalChain([ usStatesOptions, 'access', (_) => _[5], 'optionalAccess', (_2) => _2.label, ]), () => '' ) ); const [selected, setSelected] = useState(usStatesOptions[5]); const handleOnChange = ({ value }) => { setSelected(); if (value) { setInputValue(value); const filteredOptions = usStatesOptions.filter((item) => item.label.toLowerCase().includes(value.toLowerCase()) ); setSuggestedOptions(filteredOptions); } else { setInputValue(value); setSuggestedOptions(usStatesOptions); } }; const handleSelect = ({ item }) => { setInputValue(item.label); setSuggestedOptions(usStatesOptions); setSelected(item); }; return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="controlled" inputValue={inputValue} label="State" noResultText="No results for your selection" onBlur={() => { if (!selected) setInputValue(''); setSuggestedOptions(usStatesOptions); }} onChange={handleOnChange} onClear={() => { setInputValue(''); setSelected(); setSuggestedOptions(usStatesOptions); }} onSelect={handleSelect} options={suggestedOptions} placeholder="Select a US state" selectedOption={selected} /> {selected && selected.label ? ( <Box width={320}> <Text> Estimated tax to be collected in {selected && selected.label} will be calculated at checkout </Text> </Box> ) : null} </Flex> </Box> ); }
import { useState } from 'react'; import { Box, Button, ComboBox, Flex } from 'gestalt'; export default function Example() { const CATEGORIES = { BEAUTY: [ 'Beauty tips', 'DIY beauty', 'Wedding beauty', 'Vegan beauty products', 'Beauty photography', 'Beauty quotes', 'Beauty illustration', 'Beauty salon', 'Beauty blender', ].map((pronoun, index) => ({ label: pronoun, value: `value${index}` })), DIY: [ 'DIY Projects', 'DIY Art', 'DIY Home decor', 'DIY Furniture', 'DIY Gifts', 'DIY Wall decor', 'DIY Clothes', 'DIY Christmas decorations', 'DIY Christmas gifts', 'DIY Wall art', ].map((pronoun, index) => ({ label: pronoun, value: `value${index}` })), }; const [currentCategory, setCurrentCategory] = useState('BEAUTY'); const [suggestedOptions, setSuggestedOptions] = useState( CATEGORIES[currentCategory] ); const [inputValue, setInputValue] = useState(''); const [selectedOption, setSelectedOption] = useState(); const resetOptions = () => { setSuggestedOptions(CATEGORIES[currentCategory]); }; const handleOnChange = ({ value }) => { setSelectedOption(); if (value) { setInputValue(value); const filteredOptions = CATEGORIES[currentCategory].filter((item) => item.label.toLowerCase().includes(value.toLowerCase()) ); setSuggestedOptions(filteredOptions); } else { setInputValue(value); resetOptions(); } }; const handleSelect = ({ item }) => { setInputValue(item.label); setSelectedOption(item); resetOptions(); }; const handleOnBlur = () => { if (!selectedOption) setInputValue(''); resetOptions(); }; const handleOnClear = () => { setInputValue(''); setSelectedOption(); resetOptions(); }; return ( <Box height="100%" padding={2} width="100%"> <Flex alignItems="center" height="100%" justifyContent="center" width="100%" > <Flex direction="column" gap={4}> <Button onClick={() => { const nextCategory = currentCategory === 'BEAUTY' ? 'DIY' : 'BEAUTY'; setCurrentCategory(nextCategory); setSuggestedOptions(CATEGORIES[nextCategory]); setInputValue(''); }} text={`Change options to ${ currentCategory === 'BEAUTY' ? 'DIY' : 'BEAUTY' } category`} /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="programaticallySet" inputValue={inputValue} label="Pin category" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleOnClear} onSelect={handleSelect} options={suggestedOptions} placeholder="Select a category" selectedOption={selectedOption} size="lg" /> </Flex> </Flex> </Box> ); }
Size
ComboBox can have different sizes. The default size is md
(40px). The lg
size is 48px. For a dense variant, use the sm
(32px) variant.
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { const options = Array(20) .fill(0) .map((item, index) => ({ label: `Label-${index + 1}`, value: `Value-${index + 1}`, subtext: `Subtext-${index + 1}`, })); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="sm" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="md" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="lg" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="lg" /> </Flex> </Box> ); }
State
Enabled
The enabled state of Textfield that represents it can be interacted with.Error
TextField can display an error message. Simply pass in anerrorMessage
when there is an error present and TextField will handle the rest. Don't useerrorMessage
to provide feedback on character count errors. See the maximum length variant for more details.Read-only
Read-only TextFields are used to present information to the user without allowing them to edit the content. Typically they are used to show content or information that the user does not have permission or access to edit.Disabled
TextFields cannot be interacted with using the mouse or keyboard. They also do not need to meet contrast requirements, so do not use them to present info to the user (use "readOnly" instead).
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { const options = Array(20) .fill(0) .map((item, index) => ({ label: `Label-${index + 1}`, value: `Value-${index + 1}`, subtext: `Subtext-${index + 1}`, })); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="sm" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="md" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="lg" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="lg" /> </Flex> </Box> ); }
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { const options = Array(20) .fill(0) .map((item, index) => ({ label: `Label-${index + 1}`, value: `Value-${index + 1}`, subtext: `Subtext-${index + 1}`, })); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" disabled id="sm" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" disabled id="md" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" disabled id="lg" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="lg" /> </Flex> </Box> ); }
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { const options = Array(20) .fill(0) .map((item, index) => ({ label: `Label-${index + 1}`, value: `Value-${index + 1}`, subtext: `Subtext-${index + 1}`, })); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" errorMessage="Please select a valid category" id="error" label="Category" noResultText="No results for your selection" options={options} size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" errorMessage="Please select a valid category" id="error" label="Category" noResultText="No results for your selection" options={options} size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" errorMessage="Please select a valid category" id="error" label="Category" noResultText="No results for your selection" options={options} size="lg" /> </Flex> </Box> ); }
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { const options = Array(20) .fill(0) .map((item, index) => ({ label: `Label-${index + 1}`, value: `Value-${index + 1}`, subtext: `Subtext-${index + 1}`, })); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="sm" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" readOnly size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="md" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" readOnly size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="lg" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" readOnly size="lg" /> </Flex> </Box> ); }
Label
'label' is an optional prop; however, ComboBox should always be properly labelled. Learn about accessibility best practices regarding labels.
- Built-in label. Preferred. Consistent ComboBox design and tested accessibility.
In some cases, the label for a ComboBox is represented in a different way visually, as demonstrated below. We can take 2 approaches in this case.
Labelled ComboBox (Label + ComboBox). This is the best approach when a custom label is needed. The label focuses the Textfield when pressed.
Hidden built-in label (Label + Textfield). This is the best approach when there's significant visual distance between the label and the input. You can set
labelDisplay="hidden"
to ensure ComboBox is properly labeled for screen readers while using a different element to represent the label visually. The 'visual' label doesn't focus the Textfield when pressed.
import { useState } from 'react'; import { Box, ComboBox } from 'gestalt'; export default function Example() { const CATEGORIES = [ 'All Categories', 'Food and drinks', 'Beauty', 'Home decor', 'Fashion', 'Travel', 'Art', 'Quotes', 'Entertainment', 'Entertainment', 'DIY and crafts', 'Health', 'Wedding', 'Event planning', 'Gardening', 'Parenting', 'Vehicles', 'Design', 'Sport', 'Electronics', 'Animals', 'Finance', 'Architecture', ]; const options = CATEGORIES.map((category, index) => ({ label: category, value: `value${index}`, })); const [errorMessage, setErrorMessage] = useState(); const handleOnBlur = ({ value }) => { if (value !== '' && !CATEGORIES.includes(value)) setErrorMessage('Please, select a valid option'); }; const resetErrorMessage = errorMessage ? () => setErrorMessage() : () => {}; return ( <Box padding={8} width="100%"> <ComboBox accessibilityClearButtonLabel="Clear category value" errorMessage={errorMessage} id="visibleLabel" label="Choose a category to display top search trends" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={resetErrorMessage} onClear={resetErrorMessage} options={options} placeholder="Select category" /> </Box> ); }
import { useState } from 'react'; import { Box, ComboBox, Flex, Label, Text } from 'gestalt'; export default function Example() { const CATEGORIES = [ 'All Categories', 'Food and drinks', 'Beauty', 'Home decor', 'Fashion', 'Travel', 'Art', 'Quotes', 'Entertainment', 'Entertainment', 'DIY and crafts', 'Health', 'Wedding', 'Event planning', 'Gardening', 'Parenting', 'Vehicles', 'Design', 'Sport', 'Electronics', 'Animals', 'Finance', 'Architecture', ]; const options = CATEGORIES.map((category, index) => ({ label: category, value: `value${index}`, })); const [errorMessage, setErrorMessage] = useState(); const handleOnBlur = ({ value }) => { if (value !== '' && !CATEGORIES.includes(value)) setErrorMessage('Please, select a valid option'); }; const resetErrorMessage = errorMessage ? () => setErrorMessage() : () => {}; return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <Label htmlFor="externalLabel"> <Text size="300" weight="bold"> Choose a category to display top search trends </Text> </Label> <ComboBox accessibilityClearButtonLabel="Clear category value" errorMessage={errorMessage} id="externalLabel" label="" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={resetErrorMessage} onClear={resetErrorMessage} options={options} placeholder="Select category" /> </Flex> </Box> ); }
import { useState } from 'react'; import { Box, ComboBox, Flex, Heading, Link, Text } from 'gestalt'; export default function Example() { const CATEGORIES = [ 'All Categories', 'Food and drinks', 'Beauty', 'Home decor', 'Fashion', 'Travel', 'Art', 'Quotes', 'Entertainment', 'Entertainment', 'DIY and crafts', 'Health', 'Wedding', 'Event planning', 'Gardening', 'Parenting', 'Vehicles', 'Design', 'Sport', 'Electronics', 'Animals', 'Finance', 'Architecture', ]; const options = CATEGORIES.map((category, index) => ({ label: category, value: `value${index}`, })); const [errorMessage, setErrorMessage] = useState(); const handleOnBlur = ({ value }) => { if (value !== '' && !CATEGORIES.includes(value)) setErrorMessage('Please, select a valid option'); }; const resetErrorMessage = errorMessage ? () => setErrorMessage() : () => {}; return ( <Box padding={2}> <Flex direction="column" gap={4}> <Heading size="500"> Discover this week's top searched trends across all categories </Heading> <Text inline> Wanna learn how trends work? <Text inline weight="bold"> {' '} Read{' '} <Link accessibilityLabel="Learn how trends on Pinterest work" display="inlineBlock" href="https://business.pinterest.com/content/pinterest-predicts/" target="blank" > {' '} additional information </Link> </Text> </Text> <Flex justifyContent="center" width="100%"> <Box width={320}> <ComboBox accessibilityClearButtonLabel="Clear category value" errorMessage={errorMessage} id="hiddenLabel" label="Choose a category to display top search trends" labelDisplay="hidden" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={resetErrorMessage} onClear={resetErrorMessage} options={options} placeholder="Select category" /> </Box> </Flex> </Flex> </Box> ); }
Subtext
Display subtext
under each selection option
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { const options = Array(20) .fill(0) .map((item, index) => ({ label: `Label-${index + 1}`, value: `Value-${index + 1}`, subtext: `Subtext-${index + 1}`, })); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="subtext" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="subtext" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" id="subtext" label="Choose a value" noResultText="No results for your selection" options={options} placeholder="Select a value" size="lg" /> </Flex> </Box> ); }
Helper text
Whenever you want to provide more information about a form field, you should use helperText
.
import { Box, ComboBox, Flex } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox accessibilityClearButtonLabel="Clear the current value" helperText="Select one from all your current active accounts." id="helperText" label="Select account" noResultText="No results for your selection" options={[]} size="sm" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" helperText="Select one from all your current active accounts." id="helperText" label="Select account" noResultText="No results for your selection" options={[]} size="md" /> <ComboBox accessibilityClearButtonLabel="Clear the current value" helperText="Select one from all your current active accounts." id="helperText" label="Select account" noResultText="No results for your selection" options={[]} size="lg" /> </Flex> </Box> ); }
Tags
Include
Tag elements in the input using the tags
prop.
Note that the ComboBox
component doesn't internally manage tags; therefore, it must be a
controlled component. A controlled ComboBox requires three value props: options
, inputValue
, and tags
.
To use ComboBox with tags, it's recommended to create new tags on enter key presses, to remove them on backspaces when the cursor is in the beginning of the field and to filter out empty tags. These best practices are shown in the following example.
import { useRef, useState } from 'react'; import { Box, ComboBox, Flex, Tag } from 'gestalt'; export default function Example() { const ref = useRef(null); const [selected, setSelected] = useState(['he / him']); const [searchTerm, setSearchTerm] = useState(''); const PRONOUNS = [ 'ey / em', 'he / him', 'ne / nem', 'she / her', 'they / them', 've / ver', 'xe / xem', 'xie / xem', 'zie / zem', ]; const options = PRONOUNS.map((pronoun, index) => ({ label: pronoun, value: `value${index}`, })); const [suggestedOptions, setSuggestedOptions] = useState( options.filter((pronoun) => !selected.includes(pronoun.value)) ); const handleOnSelect = ({ item: { label } }) => { if (!selected.includes(label) && selected.length < 2) { const newSelected = [...selected, label]; setSelected(newSelected); setSuggestedOptions( options.filter((pronoun) => !newSelected.includes(pronoun.label)) ); setSearchTerm(''); } }; const handleOnChange = ({ value }) => { setSearchTerm(value); const suggested = value ? suggestedOptions.filter((item) => item.label.toLowerCase().includes(value.toLowerCase()) ) : options.filter((option) => !selected.includes(option.value)); setSuggestedOptions(suggested); }; const handleOnBlur = () => setSearchTerm(''); const handleClear = () => { setSelected([]); setSuggestedOptions(options); }; const handleOnKeyDown = ({ event: { keyCode, currentTarget } }) => { // Remove tag on backspace if the cursor is at the beginning of the field if (keyCode === 8 /* Backspace */ && currentTarget.selectionEnd === 0) { const newSelected = [...selected.slice(0, -1)]; setSelected(newSelected); setSuggestedOptions( options.filter((pronoun) => !newSelected.includes(pronoun.label)) ); } }; const handleRemoveTag = (removedValue) => { const newSelected = selected.filter( (tagValue) => tagValue !== removedValue ); setSelected(newSelected); setSuggestedOptions( options.filter((pronoun) => !newSelected.includes(pronoun.label)) ); }; const renderedTags = selected.map((pronoun) => ( <Tag key={pronoun} accessibilityRemoveIconLabel={`Remove ${pronoun} tag`} onRemove={() => handleRemoveTag(pronoun)} text={pronoun} /> )); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <ComboBox ref={ref} accessibilityClearButtonLabel="Clear the current value" helperText="Choose up to 2 sets of pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="tags" inputValue={searchTerm} label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleClear} onKeyDown={handleOnKeyDown} onSelect={handleOnSelect} options={suggestedOptions} placeholder={selected.length > 0 ? '' : 'Add your pronouns'} size="sm" tags={renderedTags} /> <ComboBox ref={ref} accessibilityClearButtonLabel="Clear the current value" helperText="Choose up to 2 sets of pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="tags" inputValue={searchTerm} label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleClear} onKeyDown={handleOnKeyDown} onSelect={handleOnSelect} options={suggestedOptions} placeholder={selected.length > 0 ? '' : 'Add your pronouns'} size="md" tags={renderedTags} /> <ComboBox ref={ref} accessibilityClearButtonLabel="Clear the current value" helperText="Choose up to 2 sets of pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="tags" inputValue={searchTerm} label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleClear} onKeyDown={handleOnKeyDown} onSelect={handleOnSelect} options={suggestedOptions} placeholder={selected.length > 0 ? '' : 'Add your pronouns'} size="lg" tags={renderedTags} /> <ComboBox ref={ref} accessibilityClearButtonLabel="Clear the current value" errorMessage="Select more than one option" helperText="Choose up to 2 sets of pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="tags-error" inputValue={searchTerm} label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleClear} onKeyDown={handleOnKeyDown} onSelect={handleOnSelect} options={suggestedOptions} placeholder={selected.length > 0 ? '' : 'Add your pronouns'} size="sm" tags={renderedTags} /> <ComboBox ref={ref} accessibilityClearButtonLabel="Clear the current value" helperText="Choose up to 2 sets of pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="tags-readonly" inputValue={searchTerm} label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleClear} onKeyDown={handleOnKeyDown} onSelect={handleOnSelect} options={suggestedOptions} placeholder={selected.length > 0 ? '' : 'Add your pronouns'} readOnly size="sm" tags={renderedTags} /> <ComboBox ref={ref} accessibilityClearButtonLabel="Clear the current value" disabled helperText="Choose up to 2 sets of pronouns to appear on your profile so others know how to refer to you. You can edit or remove these any time." id="tags-disabled" inputValue={searchTerm} label="Pronouns" noResultText="No results for your selection" onBlur={handleOnBlur} onChange={handleOnChange} onClear={handleClear} onKeyDown={handleOnKeyDown} onSelect={handleOnSelect} options={suggestedOptions} placeholder={selected.length > 0 ? '' : 'Add your pronouns'} size="sm" tags={renderedTags} /> </Flex> </Box> ); }
Component quality checklist
Quality item | Status | Status description |
---|---|---|
Figma Library | Ready | Component is available in Figma for web and mobile web. |
Responsive Web | Ready | Component responds to changing viewport sizes in web and mobile web. |
Related
SelectList
If users need to select from a short, simple list (without needing sections, subtext details, or the ability to filter the list), use SelectList.
Dropdown
Dropdown is an element constructed using Popover as its container. Use Dropdown to display a list of actions or options in a Popover.
Fieldset
Use Fieldset to group related form items.