Dropdown displays a list of actions, options or links. It is triggered when a user interacts with a Button, Textfield or other control. Dropdown allows for complex functionality that can’t be accomplished with SelectList.

also known as Menu, Contextual Menu

Figma:

Responsive:

Props

Component props
Name
Type
Default
children
Required
React.ChildrenArray<React.ReactElement>
-

Must be instances of Dropdown.Item, Dropdown.Link or Dropdown.Section components. See the Types of items variant to learn more.

id
Required
string
-

Unique id to identify each Dropdown. Used for Accessibility purposes.

onDismiss
Required
() => void
-

Callback fired when the menu is closed.

anchor
HTMLElement | null | undefined
-

Ref for the element that the Dropdown will attach to, will most likely be a Button. See the Accessibility guidelines to learn more.

disableMobileUI
boolean
false

Dropdown can adapt to mobile devices to SheetMobile. Mobile adaptation is disabled by default. Set to 'false' to enable SheetMobile in mobile devices. See the mobile variant to learn more.

forceDirection
boolean
false

Forces the position of Dropdown relative to its anchor element.

headerContent
React.Node
-

Content to display at the top of the Dropdown before any items or sections. See the Custom header variant to learn more.

idealDirection
"up" | "right" | "down" | "left"
"down"

Specifies the preferred position of Dropdown relative to its anchor element. See the ideal direction variant in Popover's to learn more.

isWithinFixedContainer
boolean
false

Enables correct behavior when Dropdown is used within a fixed container. To achieve this it removes the Layer component around Popover and enables positioning relative to its anchor element. Should only be used in cases where Layer breaks the Dropdown positionings such as when the anchor element is within a sticky component.

maxHeight
"30vh"
-

Define a controlled size to dropdown's Popover.

mobileOnAnimationEnd
(arg1: { animationState: "in" | "out" }) => void
-

Mobile-only prop. Callback fired when Dropdown's in & out animations end. See the mobile variant to learn more.

zIndex
Indexable
-

An object representing the zIndex value of the Dropdown menu. Learn more about zIndex classes

Usage guidelines

When to use
  • Displaying a list of actions, options, or links. Usually displays 3 or more options.
  • Allowing complex functionality that a SelectList can't accomplish.
  • Taking immediate action or navigating users to another view.
When not to use
  • In cases when there are less than 3 items in the list, and there is space to display all options. Consider RadioGroup or Checkboxes instead.
  • When it is desirable to filter a long list of options. Use ComboBox instead.
  • Displaying a list of actions or options using the browser's native select functionality. Use SelectList instead.

Best practices

Do

Use Dropdown when features such as subtext, custom headers or badges are needed, since this functionality is not available in SelectList.

Don't

Use Dropdown for a simple list of items. Use SelectList instead for the added native mobile functionality. The exception to this is multiple Dropdowns or SelectLists that could be grouped together to create visual inconsistency, such as filters. In this case, use Dropdowns for all.

Do

Order the items in Dropdown either alphabetically or by usage. Place destructive actions at the bottom.

Don't

Attach Tooltips to menu items. Use the `subtext` property if additional explanation is needed.

Do

Add an icon indicator when links are external using the isExternal prop. External links are either links outside of Pinterest or another sub-site of Pinterest.

Don't

Add custom elements within Dropdown. While some custom elements may be technically possible, it is best to avoid customization that becomes difficult to maintain.

Accessibility

ARIA attributes

Remember to include the following ARIA attributes on the element used for the anchor prop:

  • accessibilityControls: lets the screen reader know that this element controls the Dropdown menu (should match the id property passed to Dropdown). Populates the aria-controls attribute.
  • accessibilityHaspopup: lets the screen reader know that there is a Dropdown menu linked to the trigger. Populates the aria-haspopup attribute.
  • accessibilityExpanded: informs the screen reader whether the Dropdown menu is currently open or closed. Populates the aria-expanded attribute.

Keyboard interaction

  • Hitting Enter or Space key on the Dropdown's trigger opens the menu
  • Escape key closes the menu, while moving focus back on the Dropdown's trigger
  • Arrow keys are used to navigate items within the menu
  • Enter key selects an item within the Menu
  • Tab or Shift + Tab close the menu and move focus accordingly

Custom item content limitations

If using custom content, do not include interactive elements, like a TextArea or Button. Because Dropdown.Item and Dropdown.Link already act as buttons and links respectively, they cannot include focusable elements as children. Learn more about nested interactive controls

import { Fragment, useRef, useState } from 'react';
import {
  Avatar,
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
  Text,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="custom-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            icon="add"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="custom-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Section label="Currently in">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'item 1', label: 'Custom link 1' }}
            >
              <Box width="100%">
                <Flex alignItems="center" gap={2}>
                  <Avatar
                    name="Tia"
                    size="md"
                    src="https://i.ibb.co/7tGKGvb/shanice.jpg"
                  />
                  <Flex direction="column">
                    <Text>Tia Marz</Text>
                    <Text color="subtle" size="200">
                      Personal
                    </Text>
                    <Text color="subtle" size="200">
                      travel@theworld.com
                    </Text>
                  </Flex>
                </Flex>
              </Box>
            </Dropdown.Link>
          </Dropdown.Section>
          <Dropdown.Section label="Your accounts">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'item 2', label: 'Another custom link' }}
            >
              <Box width="100%">
                <Flex alignItems="center" gap={2}>
                  <Avatar
                    name="Bruno"
                    size="md"
                    src="https://i.ibb.co/4Mbhbnb/Bruno.jpg"
                  />
                  <Flex direction="column">
                    <Text>Bruno</Text>
                    <Text color="subtle" size="200">
                      Business
                    </Text>
                  </Flex>
                </Flex>
              </Box>
            </Dropdown.Link>
          </Dropdown.Section>
          <Dropdown.Section label="More options">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'settings', label: 'Settings' }}
            />
            <Dropdown.Link
              href="#"
              iconEnd="visit"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'help', label: 'Get help' }}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Fragment>
  );
}

Localization

Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.

When the text of the Dropdown.Item becomes longer than the width of the menu, either intentionally or through localization, the text will truncate at one line. Subtext will wrap as needed to display the full text.

Dropdown.Item depends on DefaultLabelProvider for internal text strings. Localize the texts via DefaultLabelProvider. Learn more
import { useRef, useState } from 'react';
import {
  Box,
  CompositeZIndex,
  DefaultLabelProvider,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  return (
    <DefaultLabelProvider
      labels={{
        Link: {
          accessibilityNewTabLabel: 'Öffnet eine neue Browser-Registerkarte.',
          accessibilityDownloadLabel: 'Es lädt eine Datei herunter.',
        },
      }}
    >
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="localization-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="Weitere Optionen"
            icon="arrow-down"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="localization-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Link
            href="https://help.pinterest.com/en?source=gear_menu_web"
            iconEnd="directional-arrow-right"
            onClick={({ event }) => event.preventDefault()}
            option={{ value: 'Hilfe anfordern', label: 'Hilfe anfordern' }}
          />
          <Dropdown.Link
            href="https://policy.pinterest.com/en/privacy-policy"
            iconEnd="directional-arrow-right"
            onClick={({ event }) => event.preventDefault()}
            option={{
              value: 'Nutzungsbedingungen und Datenschutzrichtlinien anzeigen',
              label: 'Nutzungsbedingungen und Datenschutzrichtlinien anzeigen',
            }}
          />
          <Dropdown.Link
            href="https://pinterest.com"
            iconEnd="download"
            onClick={({ event }) => event.preventDefault()}
            option={{
              value: 'Download CVS template',
              label: 'Laden Sie die CVS-Vorlage herunter',
            }}
          />
          <Dropdown.Link
            href="https://help.pinterest.com/en?source=gear_menu_web"
            iconEnd="visit"
            onClick={({ event }) => event.preventDefault()}
            option={{
              value: 'Open Google Sheets template',
              label: 'Öffnen Sie die Google Sheets-Vorlage',
            }}
          />
        </Dropdown>
      )}
    </DefaultLabelProvider>
  );
}

Subcomponents

Use Dropdown.Item for action & selection, when the Dropdown item triggers an action or selects an option.

Dropdown.Item subcomponent props
Name
Type
Default
(arg1: {
  event: React.ChangeEvent<HTMLInputElement>;
  item: {
    label: string;
    subtext?: string;
    value: string;
  };
}) => void
-

Callback when the user selects an item using the mouse or keyboard.

{
  label: string;
  subtext?: string;
  value: string;
}
-

Object detailing the label, value, and optional subtext for this item.

{
  text: string;
  type?:
    | "info"
    | "error"
    | "warning"
    | "success"
    | "neutral"
    | "recommendation"
    | "darkWash"
    | "lightWash";
}
-

When supplied, will display a Badge next to the item's label. See the Badges variant to learn more.

React.Node
-

If needed, users can supply custom content to each Dropdown Item. This can be useful when extra functionality is needed beyond a basic Link. See the Custom item content variant to learn more.

string
-

When supplied, will add a data-test-id prop to the dom element.

boolean
-

Disabled items appear inactive and cannot be interacted with.

{
    label: string;
    subtext?: string;
    value: string;
  }
| ReadonlyArray<{
    label: string;
    subtext?: string;
    value: string;
  }>
| null
-

Either the selected item info or an array of selected items, used to determine when the "selected" icon appears on an item.

Use Dropdown.Link for navigation, when the Dropdown item navigates to a new page.

Dropdown.Link subcomponent props
Name
Type
Default
string
-

Directs users to the url when item is selected. See the Types of items variant to learn more.

{
  label: string;
  subtext?: string;
  value: string;
}
-

Object detailing the label, value, and optional subtext for this item.

{
  text: string;
  type?:
    | "info"
    | "error"
    | "warning"
    | "success"
    | "neutral"
    | "recommendation"
    | "darkWash"
    | "lightWash";
}
-

When supplied, will display a Badge next to the item's label. See the Badges variant to learn more.

React.Node
-

If needed, users can supply custom content to each Dropdown Link. This can be useful when extra functionality is needed beyond a basic Link. See the Custom item content variant to learn more.

string
-

When supplied, will add a data-test-id prop to the dom element.

boolean
-

Disabled items appear inactive and cannot be interacted with.

"visit" | "directional-arrow-right" | "download"
-

An icon displayed after the text to help clarify the usage of the Dropdown Link. See the icon variant to learn more.

(arg1: {
  event: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>;
  dangerouslyDisableOnNavigation: () => void;
  mobileOnDismissStart: () => void;
}) => void
-

Callback fired when clicked (pressed and released) with a mouse or keyboard. See GlobalEventsHandlerProvider to learn more about link navigation. To learn more about mobileOnDismissStart, see the animation variant in SheetMobile. mobileOnDismissStart is the equivalent of onDismissStart in SheetMobile.

Use Dropdown.Section to create hierarchy within a single Dropdown.

Dropdown.Section subcomponent props
Name
Type
Default
React.ChildrenArray<React.ReactElement>
-

Any Dropdown.Items and/or Dropdown.Links to be rendered

string
-

Label for the section. See the Sections variant for more info.

Variants

Types of items

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const anchorRef = useRef(null);
  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <Button
            ref={anchorRef}
            accessibilityControls="action-variant-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            iconEnd="arrow-down"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
            text={selected ? selected.label : 'Display'}
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="action-variant-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Item
            onSelect={onSelect}
            option={{ value: 'Cozy', label: 'Cozy' }}
            selected={selected}
          />
          <Dropdown.Item
            onSelect={onSelect}
            option={{ value: 'Comfy', label: 'Comfy' }}
            selected={selected}
          />
        </Dropdown>
      )}
    </Fragment>
  );
}

Action/Selection

Typically a Dropdown item triggers an action, like “Hide a Pin”, or makes a selection, like “Cozy” for a layout setting. Use Dropdown.Item for these use cases. onSelect handles the user interaction, with the optional selected indicating the currently-selected item.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="link-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            icon="arrow-down"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="link-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Link
            href="https://pinterest.com"
            onClick={({ event }) => event.preventDefault()}
            option={{ value: 'Create new board', label: 'Create new board' }}
          />
          <Dropdown.Link
            href="https://help.pinterest.com/en?source=gear_menu_web"
            iconEnd="visit"
            onClick={({ event }) => event.preventDefault()}
            option={{ value: 'Get help', label: 'Get help' }}
          />
          <Dropdown.Link
            href="https://policy.pinterest.com/en/privacy-policy"
            iconEnd="visit"
            onClick={({ event }) => event.preventDefault()}
            option={{
              value: 'See terms and privacy',
              label: 'See terms and privacy',
            }}
          />
        </Dropdown>
      )}
    </Fragment>
  );
}

Link

If an item navigates to a new page, use Dropdown.Link with the required href prop. If the item navigates to a page outside of the current context, (either a non-Pinterest site or a different Pinterest sub-site), the isExternal prop should also be specified to display the "up-right" icon. Optional additional actions to be taken on navigation are handled by onClick. Dropdown.Link can be paired with GlobalEventsHandlerProvider. See GlobalEventsHandlerProvider to learn more about link navigation.

Disabled

Dropdown items can be marked as disabled. They will not receive focus and will appear inactive.

import { Fragment, useRef, useState } from 'react';
import { Box, Button, CompositeZIndex, Dropdown, FixedZIndex } from 'gestalt';

export default function CustomDisabledDropdown() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const anchorRef = useRef(null);

  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Box display="flex" justifyContent="center" margin={2} width="100%">
        <Button
          ref={anchorRef}
          accessibilityControls="demo-dropdown-example"
          accessibilityExpanded={open}
          accessibilityHaspopup
          iconEnd="arrow-down"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          size="lg"
          text="Menu"
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="demo-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Item
            onSelect={onSelect}
            option={{ value: 'Download image', label: 'Download image' }}
            selected={selected}
          />
          <Dropdown.Item
            badge={{ text: 'New' }}
            disabled
            onSelect={onSelect}
            option={{
              value: 'Hide Pin',
              label: 'Hide Pin',
              subtext: `This pin is already hidden`,
            }}
            selected={selected}
          />
          <Dropdown.Link
            disabled
            href="https://pinterest.com"
            iconEnd="visit"
            onClick={({ event }) => event.preventDefault()}
            option={{
              value: 'Report Pin',
              label: 'Report Pin',
            }}
          />
          <Dropdown.Item
            onSelect={onSelect}
            option={{ value: 'Delete Pin', label: 'Delete Pin' }}
            selected={selected}
          />
        </Dropdown>
      )}
    </Fragment>
  );
}

Sections

Dropdown can also be composed of Dropdown.Section(s), which simply require a label. Use Dropdown.Section(s) to create hierarchy within a single Dropdown. Dropdown.Sections, Dropdown.Items and Dropdown.Links can be mixed as needed.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const anchorRef = useRef(null);
  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="sections-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            bgColor="lightGray"
            icon="add"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="sections-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Section label="Create">
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Pin', label: 'Pin' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Story Pin', label: 'Story Pin' }}
              selected={selected}
            />
          </Dropdown.Section>
          <Dropdown.Section label="Add">
            <Dropdown.Item
              badge={{ text: 'New' }}
              onSelect={onSelect}
              option={{ value: 'Note', label: 'Note' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Section', label: 'Section' }}
              selected={selected}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Fragment>
  );
}

Custom header

Dropdown can also contain a custom header by specifying headerContent, which always appears at the very top of the menu. It can be used instead of a section header if the menu contains only one type of content that needs additional description. It can contain anything, but most often will contain just text and/or a link.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  Link,
  Text,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [selected, setSelected] = useState(null);
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);
  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <Button
            ref={anchorRef}
            accessibilityControls="header-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            iconEnd="arrow-down"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
            text="Menu"
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          headerContent={
            <Text align="start" size="100">
              This Pin was inspired by your{' '}
              <Text size="100" weight="bold">
                <Link href="https://pinterest.com">recent activity</Link>
              </Text>
            </Text>
          }
          id="header-dropdown-example"
          onDismiss={() => {
            setOpen(false);
          }}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Item
            onSelect={() => alert('Pin has been hidden')}
            option={{ value: 'item 1', label: 'Hide Pin' }}
            selected={selected}
          />
          <Dropdown.Link
            href="#"
            iconEnd="visit"
            onClick={({ event }) => event.preventDefault()}
            option={{
              value: 'item 2',
              label: 'Report Pin',
            }}
          />
          <Dropdown.Section label="View options">
            <Dropdown.Item
              badge={{ text: 'New' }}
              onSelect={onSelect}
              option={{ value: 'item 4', label: 'Compact' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'item 5', label: 'List' }}
              selected={selected}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Fragment>
  );
}

Subtext

Each Dropdown item can also contain subtext below the label. This subtext will wrap if needed. Use this text to add an additional description of the Dropdown item.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const anchorRef = useRef(null);
  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="subtext-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            icon="arrow-down"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="subtext-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Section label="Accounts">
            <Dropdown.Item
              onSelect={onSelect}
              option={{
                value: 'Pepper the Pupper',
                label: 'Pepper the Pupper',
                subtext: 'pepper@thepupper.com',
              }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{
                value: 'Mizu the Kitty',
                label: 'Mizu the Kitty',
                subtext: 'mizu@thekitty.com',
              }}
              selected={selected}
            />
          </Dropdown.Section>
          <Dropdown.Section label="More options">
            <Dropdown.Item
              onSelect={onSelect}
              option={{
                value: 'Tune your home feed',
                label: 'Tune your home feed',
              }}
              selected={selected}
            />
            <Dropdown.Link
              href="https://pinterest.com"
              iconEnd="visit"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'Get help', label: 'Get help' }}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Fragment>
  );
}

Badges

A Badge can be used to indicate a new product surface or feature within the Dropdown using badgeText. Multiple badges within a Dropdown should be avoided when possible.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const anchorRef = useRef(null);
  const onSelect = ({ item }) => setSelected(item);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="badges-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            icon="add"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="badges-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Section label="Create">
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Pin', label: 'Pin' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Story Pin', label: 'Story Pin' }}
              selected={selected}
            />
          </Dropdown.Section>
          <Dropdown.Section label="Add">
            <Dropdown.Item
              badge={{ text: 'New' }}
              onSelect={onSelect}
              option={{ value: 'Note', label: 'Note' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Section', label: 'Section' }}
              selected={selected}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Fragment>
  );
}

Custom item content

If needed, users can supply custom content to each Dropdown.Item or Dropdown.Link. This can be useful when extra functionality is needed, like showing an Avatar. However, please use with caution and only when absolutely necessary.

To ensure the entire width of the item is clickable, you will likely need to surround your custom content with a full-width Box.

Accessibility note: custom content cannot include interactive elements, like a TextArea or Button. Because Dropdown.Item and Dropdown.Link already act as buttons and links respectively, they cannot include focusable elements as children. Learn more
import { Fragment, useRef, useState } from 'react';
import {
  Avatar,
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
  Text,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box margin={2}>
          <IconButton
            ref={anchorRef}
            accessibilityControls="custom-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            icon="add"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="custom-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Section label="Currently in">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'item 1', label: 'Custom link 1' }}
            >
              <Box width="100%">
                <Flex alignItems="center" gap={2}>
                  <Avatar
                    name="Tia"
                    size="md"
                    src="https://i.ibb.co/7tGKGvb/shanice.jpg"
                  />
                  <Flex direction="column">
                    <Text>Tia Marz</Text>
                    <Text color="subtle" size="200">
                      Personal
                    </Text>
                    <Text color="subtle" size="200">
                      travel@theworld.com
                    </Text>
                  </Flex>
                </Flex>
              </Box>
            </Dropdown.Link>
          </Dropdown.Section>
          <Dropdown.Section label="Your accounts">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'item 2', label: 'Another custom link' }}
            >
              <Box width="100%">
                <Flex alignItems="center" gap={2}>
                  <Avatar
                    name="Bruno"
                    size="md"
                    src="https://i.ibb.co/4Mbhbnb/Bruno.jpg"
                  />
                  <Flex direction="column">
                    <Text>Bruno</Text>
                    <Text color="subtle" size="200">
                      Business
                    </Text>
                  </Flex>
                </Flex>
              </Box>
            </Dropdown.Link>
          </Dropdown.Section>
          <Dropdown.Section label="More options">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'settings', label: 'Settings' }}
            />
            <Dropdown.Link
              href="#"
              iconEnd="visit"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'help', label: 'Get help' }}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Fragment>
  );
}

Subcomponent composability

Under the hood, Dropdown executes two actions: recognizing subcomponents by display name and sequencially indexing each subcomponent for keyboard navigation.

Dropdown requires its own subcomponents as children to build the list of actions.

When building a Dropdown, we might want to render different combinations of subcomponents conditionally. Dropdown supports simple conditional rendering of subcomponents lists wrapped in React.Fragment as well as consecutive arrays of subcomponent arrays. See the example below which illustrates both of these cases. More logic complexity might break the correct Dropdown behavior.

import { Fragment, useRef, useState } from 'react';
import {
  Box,
  CompositeZIndex,
  Dropdown,
  FixedZIndex,
  Flex,
  IconButton,
  Label,
  Switch,
  Text,
} from 'gestalt';

export default function CustomIconButtonPopoverExample() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
  const [switched, setSwitched] = useState(true);
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  return (
    <Fragment>
      <Flex height="100%" justifyContent="center" width="100%">
        <Box>
          <Box margin={2}>
            <Box paddingX={2}>
              <Label htmlFor="dropdown-example">
                <Text>Toggle Dropdown subcomponents</Text>
              </Label>
            </Box>
            <Switch
              id="dropdown-example"
              onChange={() => setSwitched((value) => !value)}
              switched={switched}
            />
          </Box>
          <IconButton
            ref={anchorRef}
            accessibilityControls="custom-dropdown-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            accessibilityLabel="More Options"
            icon="add"
            iconColor="darkGray"
            onClick={() => setOpen((prevVal) => !prevVal)}
            selected={open}
            size="lg"
          />
        </Box>
      </Flex>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="custom-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          {switched ? (
            <Fragment>
              <Dropdown.Link
                href="#"
                iconEnd="visit"
                onClick={({ event }) => event.preventDefault()}
                option={{ value: 'item 1', label: 'Custom link 1' }}
              />
              <Dropdown.Link
                href="#"
                iconEnd="visit"
                onClick={({ event }) => event.preventDefault()}
                option={{ value: 'item 2', label: 'Another custom link' }}
              />
            </Fragment>
          ) : (
            <Fragment>
              {[1, 2, 3, 4, 5, 6].map((x) => (
                <Dropdown.Item
                  key={x}
                  onSelect={() => {}}
                  option={{ value: x.toString(), label: x.toString() }}
                />
              ))}
              {[7, 8, 9, 10, 11, 12].map((x) => (
                <Dropdown.Item
                  key={x}
                  onSelect={() => {}}
                  option={{ value: x.toString(), label: x.toString() }}
                />
              ))}
            </Fragment>
          )}
        </Dropdown>
      )}
    </Fragment>
  );
}

Mobile

Dropdown requires DeviceTypeProvider to enable its mobile user interface. The example below shows the mobile platform UI and its implementation.

SheetMobile has animation. To learn more about Dropdown.Link´s mobileOnDismissStart, see the animation variant in SheetMobile. mobileOnDismissStart is the equivalent of onDismissStart in SheetMobile.

import { useRef, useState } from 'react';
import {
  Avatar,
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  Dropdown,
  FixedZIndex,
  Flex,
  Link,
  Text,
} from 'gestalt';

export default function Example() {
  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);

  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  return (
    <DeviceTypeProvider deviceType="mobile">
      <Box display="flex" justifyContent="center" margin={2} width="100%">
        <Button
          ref={anchorRef}
          accessibilityControls="demo-dropdown-example"
          accessibilityExpanded={open}
          accessibilityHaspopup
          iconEnd="arrow-down"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          size="lg"
          text="Menu"
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          disableMobileUI={false}
          headerContent={
            <Text align="start" inline size="100">
              This Pin was inspired by your{' '}
              <Link display="inline" href="https://pinterest.com">
                recent activity
              </Link>
            </Text>
          }
          id="demo-dropdown-example"
          onDismiss={() => setOpen(false)}
          zIndex={new CompositeZIndex([PAGE_HEADER_ZINDEX])}
        >
          <Dropdown.Section label="Currently in">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'item 1', label: 'Custom link 1' }}
            >
              <Box width="100%">
                <Flex alignItems="center" gap={2}>
                  <Avatar
                    name="Tia"
                    size="md"
                    src="https://i.ibb.co/7tGKGvb/shanice.jpg"
                  />
                  <Flex direction="column">
                    <Text>Tia Marz</Text>
                    <Text color="subtle" size="200">
                      Personal
                    </Text>
                    <Text color="subtle" size="200">
                      travel@theworld.com
                    </Text>
                  </Flex>
                </Flex>
              </Box>
            </Dropdown.Link>
          </Dropdown.Section>
          <Dropdown.Section label="Your accounts">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'item 2', label: 'Another custom link' }}
            >
              <Box width="100%">
                <Flex alignItems="center" gap={2}>
                  <Avatar
                    name="Bruno"
                    size="md"
                    src="https://i.ibb.co/4Mbhbnb/Bruno.jpg"
                  />
                  <Flex direction="column">
                    <Text>Bruno</Text>
                    <Text color="subtle" size="200">
                      Business
                    </Text>
                  </Flex>
                </Flex>
              </Box>
            </Dropdown.Link>
          </Dropdown.Section>
          <Dropdown.Section label="More options">
            <Dropdown.Link
              href="#"
              onClick={({ event }) => event.preventDefault()}
              option={{ value: 'settings', label: 'Settings' }}
            />
            <Dropdown.Link
              href="#"
              iconEnd="visit"
              onClick={({ event, mobileOnDismissStart }) => {
                event.preventDefault();
                mobileOnDismissStart();
              }}
              option={{ value: 'help', label: 'Get help' }}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </DeviceTypeProvider>
  );
}

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Ready
Component is available in Figma for web and mobile web.
Responsive Web
Ready
Component responds to changing viewport sizes in web and mobile web.

Button, IconButton
It is most common to anchor Dropdown to Button or IconButton.

ScrollBoundaryContainer
ScrollableContainer is needed for proper positioning when the Dropdown is located within a scrolling container. The use of ScrollableContainer ensures the Dropdown remains attached to its anchor when scrolling.

SelectList
If users need to select from a short, simple list (without needing sections, subtext details, or the ability to filter the list), use SelectList.

ComboBox
If users need the ability to choose an option by typing in an input and filtering a long list of options, use ComboBox.