Multiselect
A dropdown list where users select 1 or more options.
The Basics
What it is
Multiselect enables users to enter data by selecting 1+ options from a predefined list. They can also type to filter the list of options. This limits users to a specific set of options and increases the predictability of the data users enter. Multiselect is often used in forms.
How it works
- The user clicks or focuses on Multiselect, which makes it active.
- A Forge dropdown appears, listing all available options, with a checkbox next to each one.
- Typing in the field filters the list of options.
- When the user hovers on an option, it’s highlighted.
- When the user clicks an option, its box is checked, and it appears in the field as a RemovableTag.
- To remove a selected option, the user can click the Close icon in the RemovableTag, uncheck its box in the dropdown list, or use the Delete key in the field.
- If the user types a full text string in the field and there are no matching options, “No options found” appears.
- After selecting all desired options in the list, the user navigates away from the field.
- Selections are generally not saved until another action is taken (typically by using a button, like “Submit”).
When to use
- To ask users to select 1+ options from a list
- For long lists that would take up too much vertical space on the page
When not to use
- For short lists that can appear directly on the page, reducing the number of clicks
- For lists where only 1 selection is allowed
What to use instead
Use SingleSelect for a similar component where only 1 selection is allowed.
Use SelectionList to display all list options on the page.
Use Select if users can select only 1 option.
Use HighVolumeMultiSelect when users need to sort through a high volume of options.
How to use
Multiselect can be used as a standalone component, but it’s better when used with Form and FormField. Form provides a consistent, responsive layout, and FormField adds features like labels, error message handling, and hint text.
Filtering the options list
Typing in the field automatically displays a filtered list of options that match the input. This helps users find specific options in long lists (e.g., typing “hyper” to narrow down a long list of diagnoses).
The filtering function compares the input to the text and value for every option in the list and displays the matches. For example, if an option's text is “Tylenol” and its value is “acetaminophen”, then the input “ace” is a match. If the input doesn't match anything in the list, “No options found” appears below the field.
Loading the options list
The dropdown options list can be loaded synchronously or asynchronously.
- Synchronous (on page load)
- Use for predefined lists that don’t change often.
- Loads at the same time as the rest of the page, so that when the component is activated, it displays the full list of options immediately.
- As users start typing in the field, matches appear instantly.
- Lists with hundreds or thousands of entries can slow down page load significantly.
- Asynchronous (after page load)
- Use for lists that are hosted elsewhere and change more frequently.
- Loads when users start typing (default) or when the component is activated.
- As users start typing in the field, “Loading...” appears, followed by matches.
- Reduces initial page load time but can also make the component feel slow as it searches the full list of options for matches.
- Can show a short list of predefined options when the dropdown is first opened, before users start typing. For example, a Multiselect with hundreds of medications can first display the top 5 frequently prescribed meds.
- Once users enter text into the field, the predefined options disappear.
Tips for use in forms
With FormField, the label appears to the left of Multiselect by default. The label indicates the type of data users are being asked to select (“Provider”).
Placeholder text appears inside the field before the user has interacted with it and offers short instructions for how to use the component.
Always use a label with Multiselect, and don’t use its placeholder text as a label. This is an accessibility best practice.
Use a label to indicate what data users should enter.
Use placeholder text as a label.
Style
Design details
Required fields
Forge offers 3 options to indicate required form fields. See Form for details.
Spacing and size
The height of this component and the vertical space around it vary according to the form layout (i.e., medium, compact, super-compact). See Form for details.
Placement and hierarchy
No additional information for this component.
Content
There’s no maximum length for option text. When a long option is selected and added to the input field, it’s automatically truncated to one line of text. Use sentence case for option text (“Abdominal pain”, not “Abdominal Pain”).
Use sentence case for label text (“Patient care team”, not “Patient Care Team”).
Use sentence case for placeholder text. It defaults to “- Type or Select -”, but you can change it to reflect the content of your list (“- Type or select patient names -”).
If no options match the input, “No options found” appears. You can change this to reflect the content of your list (“No patients found”).
If loading the options list asynchronously, “Loading...” appears at the top of the list by default, but you can change this to reflect the content of your list (“Loading patients…”).
Demos
Coding
Developer tips
Multiselect uses the react-select library. Any props not used by Multiselect (see Props table) are passed down to react-select. It can be used on its own or as an input in FormField.
Known issue with dropdown
The react-select library has a known dropdown issue, where the dropdown remains at a fixed position if the parent container has a scrollbar independent from the rest of the window. This can occur when using the CSS property overflow:auto
. Until this is fixed, we suggest using the disablePortal
prop as a workaround, unless you’re using Multiselect in Lightbox or Modal (this workaround isn’t suitable for these 2 components).
Asynchronous usage
- Filtering
- When loading options asynchronously, Multiselect doesn’t automatically handle option filtering (matching the user input string to option text and values). You must build this into the
loadAsyncOptions
function. See the Demos for an example of option filtering for asynchronous use.
- When loading options asynchronously, Multiselect doesn’t automatically handle option filtering (matching the user input string to option text and values). You must build this into the
- Loading options on input vs. loading options on mount
- By default, Multiselect doesn’t load options until users start typing in the input field. To display options before user input (on click or focus), use the
initialAsyncOptions
prop. - You can provide
initialAsyncOptions
with a predefined set of options to display or setinitialAsyncOptions
to{true}
, which tells Multiselect to load options on mount using the function provided to theloadAsyncOptions
prop.
- By default, Multiselect doesn’t load options until users start typing in the input field. To display options before user input (on click or focus), use the
Caching
Multiselect sets the cacheOptions
prop to {true}
by default. This causes the component to cache the options that are loaded for each unique input. Set this prop to {false}
to disable caching. To maintain good application performance, pay attention to the size of your cache. cacheOptions
continues to cache loaded options until the prop’s value changes; it can be reset or altered manually at any time.
Memoization
It is important to use functions such as useMemo
to to not generate new option values on every render. Otherwise keyboard navigation does not work as well as it should.
Required fields
Use the required
prop to mark Multiselect as required.
Forge offers 3 options to indicate required form fields. When using Form with this component, set Form’s requiredVariation
prop. See Form for details.
Repository
Implementation links
Multiselect directory in Bitbucket
Implementation details
It is strongly recommended to familiarize yourself with the Forge source code. While this documentation is a best effort to document the intent and usage of a component, sometimes some features only become clear when looking at the source code. Also, looking at Forge's source code may help identify and fix bugs in either your application or Forge itself.
Storybook files
Forge maintains at least one storybook file per component. While the primary audience for these files is typically the Forge team, these storybook files may cover usages of the component not covered by a demo. The storybook for the latest version of forge can be found at go/forge-storybook-lts.
Testing library
Forge strongly encourages using testing-library to write tests for your application.
"The more your tests resemble the way your software is used, the more confidence they can give you."
If you're having trouble testing a Forge component using testing-library, it would be a good idea to see how Forge tests its own components. For the most part, Forge tries to use screen.getByRole as much as it can, as that API provides the best feedback on a11y compliance. Forge discourages the use of document.querySelector and screen.getByTestId as both APIs encourage using implementation details to test your component, and discourage adding roles to your component.
With that being said, many of Forge's components were not built with accessibility in mind. These components do break the recommendations listed above.
Import statements
In Nimbus applications
athenaOne serves the Forge bundle independently from your application's bundle. Importing Forge components directly from '@athena/forge'
takes advantage of this feature.
import { Multiselect } from '@athena/forge'
In standalone applications
Importing components using the exact path to the module takes advantage of webpack's tree shaking feature. Webpack will include only that module and its dependencies.
import Multiselect from '@athena/forge/Multiselect';
To use this import guidance, Typescript applications must use typescript >= 4.7.3
, and should add this setting to their tsconfig.json file:
{"compilerOptions": {"moduleResolution": "Node16",}}
If this setting doesn't work for your application, use this import statement instead:
import Multiselect from '@athena/forge/dist/Multiselect';