Select Menu

View on GitHub

Introduction

The SelectMenu component is an advanced interaction pattern which allows selection of multiple items from a dropdown list. It can be used as a substitute for the native multiple select element.

Implementation details

The SelectMenu builds on top of the Popover component and uses react-tiny-virtual-list for the rendering of the virtualized list of options.

Multiselect

The SelectMenu is unopinionated in how many items are selected in the list. Pass an array to the selected prop to select more items.

Options prop structure

const options = [
{
label: 'String',
value: 'String or Number'
}
]

Single selected item

This example shows basic usage with a single selected item.

The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <SelectMenu
      title="Select name"
      options={
        ['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber']
          .map(label => ({ label, value: label }))
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
  )}
</Component>
Hide code

Remove title and filter

  • hasFilter={false}: to remove the search input filter.
  • hasTitle={false}: to remove the title from the popover.
The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <SelectMenu
      hasTitle={false}
      hasFilter={false}
      title="Select name"
      options={
        ['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber']
          .map(label => ({ label, value: label }))
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
  )}
</Component>
Hide code

Change the height and width

The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <SelectMenu
      height={140}
      width={180}
      hasTitle={false}
      hasFilter={false}
      title="Select name"
      options={
        ['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber']
          .map(label => ({ label, value: label }))
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
  )}
</Component>
Hide code

Change the position of the popover

Available positions:

  • Position.TOP
  • Position.TOP_LEFT
  • Position.TOP_RIGHT
  • Position.BOTTOM
  • Position.BOTTOM_LEFT
  • Position.BOTTOM_RIGHT
The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <SelectMenu
      position={Position.TOP}
      title="Select name"
      options={
        ['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber']
          .map(label => ({ label, value: label }))
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
  )}
</Component>
Hide code

Empty view

It's possible to display a custom empty view instead of options list via emptyView, when there are no properties supplied. Note that empty view won't be shown when options are being filtered and there are no search results.

<SelectMenu
  title="Empty view"
  options={[]}
  emptyView={(
    <Pane height="100%" display="flex" alignItems="center" justifyContent="center">
      <Text size={300}>No options found</Text>
    </Pane>
  )}
>
  <Button>Select option...</Button>
</SelectMenu>
Hide code

It's also possible to close <SelectMenu> from within empty view:

<SelectMenu
  title="Empty view"
  options={[]}
  emptyView={({ close }) => (
    <Pane height="100%" display="flex" alignItems="center" justifyContent="center">
      <Button onClick={close}>Close</Button>
    </Pane>
  )}
>
  <Button>Select option...</Button>
</SelectMenu>
Hide code

Multiselect with deselect example

This example shows usage with multiple selected items.

This pattern is only an example. Selected values and the formatting of their names should be managed wherever you choose to manage state. The onDeselect method is provided to assist with this.

As users click on selected values to remove them, you can update state.

The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component
  initialState={{
    options: ['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber']
          .map(label => ({ label, value: label })),
    selected: []
  }}
>
  {({ state, setState }) => (
    <SelectMenu
      isMultiSelect
      title="Select multiple names"
      options={state.options}
      selected={state.selected}
      onSelect={item => {
        const selected = [...state.selected, item.value]
        const selectedItems = selected
        const selectedItemsLength = selectedItems.length
        let selectedNames = ''
        if (selectedItemsLength === 0) {
          selectedNames = ''
        } else if (selectedItemsLength === 1) {
          selectedNames = selectedItems.toString()
        } else if (selectedItemsLength > 1) {
          selectedNames = selectedItemsLength.toString() + ' selected...'
        }
        setState({
          selected,
          selectedNames
        })
      }}
      onDeselect={item => {
        const deselectedItemIndex = state.selected.indexOf(item.value)
        const selectedItems = state.selected.filter(
          (_item, i) => i !== deselectedItemIndex
        )
        const selectedItemsLength = selectedItems.length
        let selectedNames = ''
        if (selectedItemsLength === 0) {
          selectedNames = ''
        } else if (selectedItemsLength === 1) {
          selectedNames = selectedItems.toString()
        } else if (selectedItemsLength > 1) {
          selectedNames = selectedItemsLength.toString() + ' selected...'
        }
        setState({ selected: selectedItems, selectedNames })
      }}
    >
      <Button>{state.selectedNames || 'Select multiple...'}</Button>
    </SelectMenu>
  )}
</Component>
Hide code

onFilterChange example

This example shows basic usage with onFocusChange.

Filter value:
The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <Pane>
    <Pane marginBottom={8}>
      <Text>Filter value: {state.filter}</Text>
    </Pane>
    <SelectMenu
      title="Select name"
      onFilterChange={filter => setState({filter})}
      options={
        ['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber']
          .map(label => ({ label, value: label }))
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
    </Pane>
  )}

</Component>
Hide code

Disabled option example

This example shows basic usage for disabling some options. Options that are disabled cannot be clicked and their labels are muted.

The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <Pane>
    <SelectMenu
      title="Select Option"
      options={
        [{ label: "Disabled", value: "disabled", disabled: true }, { label: "Not Disabled", value: "not-disabled" }]
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
    </Pane>
  )}

</Component>
Hide code

Custom Title Example

This example shows how one should use titleView to pass in a custom title.

The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <Pane>
    <SelectMenu
      title="Select Option"
      tooltipContent="Choose one of the options below."
      titleView={({ close, title, headerHeight }) => {
        return (
          <Pane
            display="flex"
            alignItems="center"
            borderBottom="default"
            padding={8}
            height={headerHeight}
            boxSizing="border-box"
          >
            <Pane flex="1" display="flex" alignItems="center">
              <Heading size={400}>{title}</Heading>
                <Tooltip content="Pick one of the options below">
                  <Icon size={12} marginLeft={4} icon="help" />
                </Tooltip>
            </Pane>
            <IconButton
              icon="cross"
              appearance="minimal"
              height={24}
              onClick={close}
            />
          </Pane>
        )
      }}
      options={
        [{ label: "Option 1", value: "option-1"}, { label: "Option 2", value: "option-2" }]
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
    </Pane>
  )}

</Component>
Hide code

Custom Filter PlaceHolder and Icon Example

It's possible to change the filter placeholder and filter icon through the filterPlaceholder and filterIcon props.

Note that the icon can be one found in Segment's various icons, or none.

The `Component` component is not part of Evergreen. It is only used in examples to create state. Learn more.
<Component initialState={{ selected: null }}>
  {({ setState, state }) => (
    <SelectMenu
      title="Select name"
      options={
        ['Comedy', 'Drama', 'Fantasy', 'Family', 'Action']
          .map(label => ({ label, value: label }))
      }
      selected={state.selected}
      onSelect={item => setState({ selected: item.value })}
      filterPlaceholder={"Choose a genre"}
      filterIcon={"film"}
    >
      <Button>{state.selected || 'Select name...'}</Button>
    </SelectMenu>
  )}
</Component>
Hide code

SelectMenu Props

titlestring
The title of the Select Menu.
widthnumber = 240
The width of the Select Menu.
heightnumber = 248
The height of the Select Menu.
optionsarrayOfOptionShapePropType
The options to show in the menu. [{ label: String, value: String | Number }]
onSelectfunc = () => {}
Function that is called when an option is selected.
onDeselectfunc = () => {}
Function that is called when an option is deselected.
selectedcustom
The selected value/values.
isMultiSelectbool = false
When true, multi select is accounted for.
hasTitlebool
When true, show the title.
hasFilterbool
When true, show the filter.
filterPlaceholderstring = 'Filter...'
The placeholder of the search filter.
filterIconstring = 'search'
The icon of the search filter.
onFilterChangefunc
Function that is called as the onChange() event for the filter.
positionenum = Position.BOTTOM_LEFT
The position of the Select Menu.
detailViewunion
Can be a function that returns a node, or a node itself, that is rendered on the right side of the Select Menu to give additional information when an option is selected.
titleViewunion
Can be a function that returns a node, or a node itself, that is rendered in the header section of the Select Menu to customize the header.
emptyViewunion
Can be a function that returns a node, or a node itself, that is rendered instead of the options list when there are no options.