Dropdown presents a list of actions after a user interacts with a trigger.
Examples #
Import Syntax #
import Dropdown from 'mineral-ui/Dropdown';
Basic Usage #
Placement is relative to the trigger. Dropdowns will automatically change position relative to the trigger depending on viewport constraints.
<Dropdown data={data}> <Button>Menu</Button> </Dropdown>
Data-Driven #
Dropdown content is defined by an array of data, with the structure shown in the code example below. Object properties will be passed on to the MenuItem.
MenuDividers are created simply by passing
{ divider: true }
as an item.
MenuGroups are created simply by passing
{ group: true, title: 'Group Title' }
as an item.
() => { const data = [ { text: 'Menu item with onClick', onClick: event => { console.log(event); } }, { text: 'Menu item', secondaryText: 'Secondary text' }, { text: 'Icon at start', iconStart: <IconCloud /> }, { text: 'Icon at end', iconEnd: <IconCloud /> }, { divider: true }, { text: 'Disabled menu item', onClick: () => { console.log('onClick is not triggered for disabled MenuItems'); }, disabled: true }, { title: 'Group Title', group: true }, { text: 'Success variant', variant: 'success' }, { text: 'Warning variant', variant: 'warning' }, { text: 'Danger variant', variant: 'danger' } ]; return ( <Dropdown data={data}> <Button>Menu</Button> </Dropdown> ); }
Wide #
Dropdown can display a wider menu (~50% wider). Use this when there are MenuItems that wrap and look odd. Make sure you have enough room for the wider Menu on the devices you're supporting.
<DemoLayout> <Dropdown wide isOpen data={data}> <Button>Menu</Button> </Dropdown> </DemoLayout>
Placement #
The placement
prop determines the initial placement of the Dropdown content relative to the trigger.
The Dropdown will react to viewport edges and scrolling.
<DemoLayout> <Dropdown placement="bottom-start" data={data} isOpen> <Button>Menu</Button> </Dropdown> </DemoLayout>
Overflow #
A Dropdown can extend beyond its bounding container (the blue area in this example) even if the container has an overflow: hidden
style. See the portal example for even greater control.
<OverflowContainer> <Dropdown data={data} isOpen> <Button>Menu</Button> </Dropdown> </OverflowContainer>
Scrolling container #
Dropdown will attempt to keep its menu visible in an overflow: scroll
container.
<ScrollParent height={430}> <Dropdown data={data} isOpen placement="bottom-start"> <Button>Menu</Button> </Dropdown> </ScrollParent>
Portal #
Use a portal to render the Dropdown to the body of the page rather than as a sibling of the trigger. This can be useful to visually "break out" of a bounding container with overflow
or z-index
styles. Note that you may have to adjust the modifiers
to get the exact behavior that you want.
<ScrollParent> <Dropdown data={data} usePortal isOpen modifiers={{ preventOverflow: { escapeWithReference: true } }}> <Button>Menu</Button> </Dropdown> </ScrollParent>
Disabled #
Disable the Dropdown and its trigger.
<Dropdown data={data} disabled> <Button>Menu</Button> </Dropdown>
Controlled #
Dropdown controls its own state by default,
and can optionally be managed by the application as a controlled component through the isOpen
prop.
Callbacks for onOpen
and onClose
are also provided.
class App extends Component { constructor(props) { super(props); this.state = { isOpen: false }; this.onOpen = this.onOpen.bind(this); this.onClose = this.onClose.bind(this); this.toggleDropdown = this.toggleDropdown.bind(this); } onOpen(event) { this.setState({ isOpen: true }); } onClose(event) { // Prevent extra call to toggleDropdown when clicking the controlling button. // Also avoid interactions with other dropdowns. const demoLayoutNode = this.demoLayout; if ( !event.nativeEvent && demoLayoutNode && demoLayoutNode.contains(event.target) ) { event.stopImmediatePropagation(); } this.setState({ isOpen: false }); } toggleDropdown(event) { if (this.state.isOpen) { this.onClose(event); } else { this.onOpen(event); } } render() { const label = this.state.isOpen ? 'Close Dropdown' : 'Open Dropdown'; return ( <DemoLayout ref={node => { this.demoLayout = node }}> <Dropdown data={data} isOpen={this.state.isOpen} onOpen={this.onOpen} onClose={this.onClose}> <Button>{label}</Button> </Dropdown> <Button onClick={this.toggleDropdown}>{label}</Button> </DemoLayout> ); } }
Bidirectionality #
Dropdowns support right-to-left (RTL) languages. The placement of
the menu as well as the menu itself will be reversed when the direction
theme variable is set to rtl
.
<DemoLayout dir="rtl"> <ThemeProvider theme={{ direction: 'rtl' }}> <Dropdown data={data} isOpen> <Button>Menu</Button> </Dropdown> </ThemeProvider> </DemoLayout>
Custom Item #
Use the item
render prop to provide custom
rendering control of all MenuItems in the Menu.
See our Render Props Guide for additional information, including important considerations and examples.
The implementation of item
used in the following example can be seen
in full in the Menu example.
() => { const data = [ { avatar: '/images/avatar.svg', text: 'Newton', work: 'Principia Mathematica' }, { avatar: '/images/avatar.svg', text: 'Descartes', work: 'La Géométrie' }, { avatar: '/images/avatar.svg', text: 'Euclid', work: 'Elements' } ]; return ( <DemoLayout> <Dropdown data={data} item={item} isOpen> <Button>Menu</Button> </Dropdown> </DemoLayout> ); }
Custom Menu #
Use the menu
render prop to provide custom rendering control
of the Menu. See our Render Props Guide for additional information, including important considerations and examples.
Note
The menu in the example below has been customized to include a search input. While neither fully functional nor accessible, it hopefully serves to give the user an idea of something that could be achieved with this technique.() => { const menu = ({ props }) => { const Search = styled('div')(({ theme }) => ({ borderBottom: '1px solid ' + theme.color_gray_40, padding: theme.space_inset_md })); return ( <div> <Search> <FormField input={TextInput} type="search" label="Search" placeholder="Search..." hideLabel iconEnd={<IconSearch />} /> </Search> <Menu {...props} /> </div> ); }; return ( <DemoLayout> <Dropdown data={data} menu={menu} isOpen> <Button>Menu</Button> </Dropdown> </DemoLayout> ); }
Custom Trigger #
Use the trigger
render prop to provide custom rendering
control of the trigger. See our Render Props Guide for additional information, including important considerations and examples.
() => { // Your render function must return a Popper Reference component. // import { Reference } from 'react-popper'; const CustomTrigger = styled('button')(); return ( <Dropdown data={data}> { ({ props, state }) => { return ( <Reference> {({ ref: popperRef }) => { const triggerProps = { ...props, ref: (node) => { popperRef(node); props.ref(node); }, role: undefined }; return ( <CustomTrigger {...triggerProps}> Menu <span role="img" aria-hidden="true">{state.isOpen ? '▲' : '▼'}</span> </CustomTrigger> ); }} </Reference> ); } } </Dropdown> ); }
API & Theme #
Dropdown Props #
The Dropdown component takes the following React props.
Name | Type | Default | Description |
---|---|---|---|
children | React$Node | RenderFn | required | Trigger for the Dropdown. Optionally provides custom rendering control. See the custom trigger example and our render props guide. |
data | Items | required | |
defaultHighlightedIndex | number | Index of item to be highlighted upon initialization. Primarily for use with uncontrolled components. | |
defaultIsOpen | boolean | Open the Dropdown upon initialization. Primarily for use with uncontrolled components. | |
disabled | boolean | Disable the Dropdown | |
highlightedIndex | number | Index of the highlighted item. For use with controlled components. | |
id | string | Id of the Dropdown | |
isOpen | boolean | Determines whether the Dropdown is open. For use with controlled components. | |
item | Provides custom rendering control for the items. See the custom item example and our render props guide. | ||
itemKey | string | 'text' | Specifies a key in the item data that gives an item its unique identity. See the React docs. |
menu | Provides custom rendering control for the menu. See the custom menu example and our render props guide. | ||
modifiers | Object | Plugins that are used to alter behavior. See PopperJS docs for options. | |
onClose | Called when Dropdown is closed | ||
onOpen | Called when Dropdown is opened | ||
placement | 'bottom-start' | Placement of the Dropdown menu | |
positionFixed | boolean | Apply fixed positioning to the menu | |
usePortal | boolean | Use a Portal to render the Dropdown menu to the body rather than as a sibling to the trigger | |
wide | boolean | Display a wider Dropdown menu |
Dropdown Theme Variables #
These variables can be used as hooks to override this component's
style at either a
local
or global
level. The theme
referenced below is whatever theme is available
from props to the instance of this component.
Variable | Value |
---|---|
DropdownContent_backgroundColor | theme.panel_backgroundColor |
DropdownContent_borderColor | theme.panel_borderColor |
DropdownContent_borderRadius | theme.borderRadius_1 |
DropdownContent_boxShadow | theme.boxShadow_2 |
DropdownContent_margin | 5px |
DropdownContent_zIndex | theme.zIndex_100 |
Usage #
When/How to Use #
Dropdowns can change their available MenuItems depending on the
state of your app. You can remove options that don't make sense in the current
context, or show them as disabled
if certain conditions need to be met.
Use Dropdown for extra functionality, not for primary actions, since the options are hidden from the user until interaction. Ensure that option labeling clearly describes the action the user can take.
Best Practices #
Populate Dropdown with options that are related to the trigger, so users can find actions easily.
Don't present a Dropdown with only one option. If your app renders only one option depending on state, consider a different layout for that special case.
Don't mix navigational and action items in Dropdown. If using Dropdown for navigational purposes, consider using a navigation component instead.