Dropdown

An accessible, custom alternative to the native Select component.

React

The Dropdown component provides a more flexible alternative to the native Select component to implement things like populating options from a search API request.

Multiple Selection

We currently only support selecting a single item, if you need multi-select support, please request it here.

Installation

The Dropdown component is available as a separate package, @hoverinc/dropdown that depends on @hoverinc/design-system-react-web.

yarn add @hoverinc/dropdown

Theme

The theme for the Dropdown component must also be added to your DesignSystemProvider

import { DesignSystemProvider } from '@hoverinc/design-system-react-web'; import { Dropdown } from '@hoverinc/design-system-react-web/extra/theme'; interface AppProps { children: React.ReactNode; } const App = ({ children }: AppProps) => ( <DesignSystemProvider extraComponentThemes={{ Dropdown }}> {children} </DesignSystemProvider> ); export { App };

Simple Select

Dropdown can behave just like a simple native Select component, albeit a bit prettier. That said, a native Select might be the right choice here. See below for use cases where Dropdown shines.

The inPortal prop tells Dropdown to open its menu in a Portal in cases where overflow: hidden or adjacent components may interfere with it.

Required Props

The itemToString and itemToValue props are required and determine what will be displayed and the value persisted in the internal form element

<Dropdown inPortal itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={[ { title: 'One', id: 1 }, { title: 'Two', id: 2 }, { title: 'Three', id: 3 }, { title: 'Four', id: 4 }, ]} placeholder="Select..." />

Within a Field

Wrap Dropdown in a Field to get the same floating label, error and helper text treatment as a Field around a native Select. Pass the label to Field and it renders inside the control, floating up out of the way once a value is selected.

No extra wiring is needed: do not add your own FieldLabel, and do not override .hover-dropdown styles to make room for the label.

<Field label="Bird" name="bird"> <Dropdown inPortal itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={mocks.dropdown} /> </Field>
Omit the placeholder

Inside a Field, the label already occupies the control when it is empty, so a placeholder would sit behind it. Leave placeholder unset and let the label do that job — the same as Field + Select.

An error on the Field styles the control as invalid and is announced to assistive technology.

<Field error="Please select a bird" label="Bird" name="bird"> <Dropdown inPortal itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={mocks.dropdown} /> </Field>
Disabled state

Dropdown does not support isDisabled yet, so a disabled Field will not disable it. Use isItemDisabled to disable individual items.

Filtering

Use the onInputValue change prop to implement filtering on items.

() => { const mockItems = mocks.dropdown; const [items, setItems] = useState(mockItems); return ( <Dropdown inPortal itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={items} onInputValueChange={({ inputValue }) => { setItems( inputValue ? mockItems.filter(({ title }) => title.toLowerCase().includes(inputValue.toLowerCase()), ) : mockItems, ); }} placeholder="Filtering..." /> ); };

Asynchronous

Set the isLoading prop to indicate the state of an asynchronous request when populating items from an API.

() => { const mockItems = mocks.dropdown; const [isLoading, setIsLoading] = useState(false); const [items, setItems] = useState(mockItems); return ( <Dropdown isLoading={isLoading} inPortal items={items} itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} onInputValueChange={({ inputValue }) => { setIsLoading(true); setTimeout(() => { setIsLoading(false); setItems( inputValue ? mockItems.filter(({ title }) => title.toLowerCase().includes(inputValue.toLowerCase()), ) : mockItems, ); }, 500); }} placeholder="Asynchronous" /> ); };

Icon

Like other form inputs, Dropdown supports displaying an icon in the input.

<Dropdown inPortal iconBefore={iconFilter} itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={[ { title: 'One', id: 1 }, { title: 'Two', id: 2 }, { title: 'Three', id: 3 }, { title: 'Four', id: 4 }, ]} placeholder="Select..." />

Item Icons

Include a system icon as the icon property in items to render an icons in the list.

<Dropdown inPortal iconBefore={iconFilter} itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={[ { title: 'One', id: 1, icon: iconHeartFilled }, { title: 'Two', id: 2, icon: iconUser }, { title: 'Three', id: 3, icon: iconFile }, { title: 'Four', id: 4, icon: iconExternalLink }, ]} placeholder="Select..." />

Combobox

  • actAsCombobox keeps the text input visible after selection and preserves the typed value on blur or Escape.

Autocomplete Mode (actAsCombobox)

Use actAsCombobox when the dropdown should behave like an autocomplete input.

() => { const mockItems = mocks.dropdown; const [items, setItems] = useState(mockItems); return ( <Dropdown actAsCombobox inPortal itemToString={item => (item ? item.title : '')} itemToValue={({ id }) => id} items={items} onInputValueChange={({ inputValue }) => { setItems( inputValue ? mockItems.filter(({ title }) => title.toLowerCase().includes(inputValue.toLowerCase()), ) : mockItems, ); }} placeholder="Search..." /> ); };

React Native

Not implemented in React Native

Status: Figma/web only — not implemented in React Native. This component has no equivalent in @hoverinc/design-system-react-native today.