Components

Dropdown

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.

NameTypeDefaultDescription
childrenReact$Node | RenderFnrequired

Trigger for the Dropdown. Optionally provides custom rendering control. See the custom trigger example and our render props guide.

dataItemsrequired

Data from which the Menu will be constructed (see example)

defaultHighlightedIndexnumber

Index of item to be highlighted upon initialization. Primarily for use with uncontrolled components.

defaultIsOpenboolean

Open the Dropdown upon initialization. Primarily for use with uncontrolled components.

disabledboolean

Disable the Dropdown

highlightedIndexnumber

Index of the highlighted item. For use with controlled components.

idstring

Id of the Dropdown

isOpenboolean

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.

itemKeystring'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.

modifiersObject

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

positionFixedboolean

Apply fixed positioning to the menu

usePortalboolean

Use a Portal to render the Dropdown menu to the body rather than as a sibling to the trigger

wideboolean

Display a wider Dropdown menu

Undocumented properties, including as and css, will be applied to the root element.

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.

VariableValue
DropdownContent_backgroundColortheme.panel_backgroundColor
DropdownContent_borderColortheme.panel_borderColor
DropdownContent_borderRadiustheme.borderRadius_1
DropdownContent_boxShadowtheme.boxShadow_2
DropdownContent_margin5px
DropdownContent_zIndextheme.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.