Skip to content

DropdownMenu

TIP

If you need to customize your work of the dropdown menu, refer to the documentation for intergalactic/popper

The component is a wrapper over the intergalactic/dropdown that allows for the following:

  • Displaying a list of options in a dropdown
  • Scrolling through the list of options using keyboard

Basic usage

tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import Button from 'intergalactic/button';

const Demo = () => {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>Actions</DropdownMenu.Trigger>
      <DropdownMenu.Menu>
        <DropdownMenu.Item>Save</DropdownMenu.Item>
        <DropdownMenu.Item>Rename</DropdownMenu.Item>
        <DropdownMenu.Item>Download</DropdownMenu.Item>
        <DropdownMenu.Item>Delete</DropdownMenu.Item>
      </DropdownMenu.Menu>
    </DropdownMenu>
  );
};

export default Demo;

There are a few ways to display the dropdown menu in this component.

First method

Use a combination of two components:

  • DropdownMenu.Popper—for the dropdown layout
  • DropdownMenu.List and ScrollArea—for the option list styles

This method works well when you need flexible customization of the dropdown menu content.

tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import Button from 'intergalactic/button';
import Link from 'intergalactic/link';
import { Text } from 'intergalactic/typography';
import Notice from 'intergalactic/notice';
import SpinContainer from 'intergalactic/spin-container';
import FileExportM from 'intergalactic/icon/FileExport/m';

const Demo = () => {
  const [loading, setLoading] = React.useState(false);

  const handleClick = () => {
    setLoading(true);
    setTimeout(() => setLoading(false), 1000);
  };

  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>
        <Button.Addon>
          <FileExportM />
        </Button.Addon>
        <Button.Text>Export</Button.Text>
      </DropdownMenu.Trigger>
      <DropdownMenu.Popper wMax='256px'>
        <SpinContainer loading={loading}>
          <DropdownMenu.List>
            <DropdownMenu.Item onClick={handleClick}>Excel</DropdownMenu.Item>
            <DropdownMenu.Item onClick={handleClick}>CSV</DropdownMenu.Item>
            <DropdownMenu.Item onClick={handleClick}>CSV Semicolon</DropdownMenu.Item>
          </DropdownMenu.List>
          <Notice
            theme='warning'
            style={{
              padding: 'var(--intergalactic-spacing-3x) var(--intergalactic-spacing-2x)',
              borderWidth: 0,
              borderTopWidth: '1px',
              borderRadius:
                '0 0 var(--intergalactic-rounded-medium) var(--intergalactic-rounded-medium)',
            }}
          >
            <Notice.Content>
              <Text tag='strong' mb={1} style={{ display: 'block' }}>
                Export failed
              </Text>
              <Text>
                If the problem persists, please contact us at{' '}
                <Link inline href='mailto:feedback@semrush.com'>
                  feedback@semrush.com
                </Link>
              </Text>
            </Notice.Content>
          </Notice>
        </SpinContainer>
      </DropdownMenu.Popper>
    </DropdownMenu>
  );
};

export default Demo;

Second method

The easiest way is to use DropdownMenu.Menu.

This is best when you only need to manage the content within the options list.

DropdownMenu.Menu is a wrapper around DropdownMenu.Popper and DropdownMenu.List, and all props pass through to DropdownMenu.List.

tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import { ButtonTrigger } from 'intergalactic/base-trigger';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const Demo = () => {
  return (
    <Flex direction='column'>
      <Text tag='label' size={200} htmlFor='dropdown-menu-children-items'>
        Your choice
      </Text>
      <DropdownMenu>
        <DropdownMenu.Trigger
          tag={ButtonTrigger}
          mt={2}
          mr='auto'
          id='dropdown-menu-children-items'
        >
          Choose an item
        </DropdownMenu.Trigger>
        {/* Adding max-height to the dropdown menu */}
        <DropdownMenu.Menu hMax={'180px'}>
          <DropdownMenu.Group title={'List heading'} subTitle={'Subtitle'}>
            <DropdownMenu.Item>Item 1</DropdownMenu.Item>
            <DropdownMenu.Item>Item 2</DropdownMenu.Item>
            <DropdownMenu.Item>Item 3</DropdownMenu.Item>
            <DropdownMenu.Item>Item 4</DropdownMenu.Item>
            <DropdownMenu.Item>Item 5</DropdownMenu.Item>
            <DropdownMenu.Item>Item 6</DropdownMenu.Item>
            <DropdownMenu.Item>Item 7</DropdownMenu.Item>
            <DropdownMenu.Item>Item 8</DropdownMenu.Item>
            <DropdownMenu.Item>Item 9</DropdownMenu.Item>
          </DropdownMenu.Group>
        </DropdownMenu.Menu>
      </DropdownMenu>
    </Flex>
  );
};

export default Demo;

The component offers several options for laying out list item types:

  • DropdownMenu.Item: A list element that can be selected with the keyboard.
  • DropdownMenu.Item.Content: The content within an item, used when you need to include a hint or submenu.
  • DropdownMenu.Item.Hint: A subheading or message with additional information (can't be selected with the keyboard).
tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import Button from 'intergalactic/button';
import Tooltip from 'intergalactic/tooltip';

const TooltipContent = () => {
  const tooltipIndex = React.useContext(DropdownMenu.selectedIndexContext);

  return <div>Some tooltip for {tooltipIndex + 1}</div>;
};

const Demo = () => {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>Check the options</DropdownMenu.Trigger>
      <DropdownMenu.Menu>
        <Tooltip placement={'right'} timeout={[0, 50]}>
          <DropdownMenu.Group title={'Title'} subTitle={'Subtitle'}>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Item 1</DropdownMenu.Item.Content>
            </DropdownMenu.Item>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Item 2</DropdownMenu.Item.Content>
              <DropdownMenu.Item.Hint>Hint for item 2</DropdownMenu.Item.Hint>
            </DropdownMenu.Item>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Item 3</DropdownMenu.Item.Content>
              <DropdownMenu.Item.Hint>Hint for item 3</DropdownMenu.Item.Hint>
            </DropdownMenu.Item>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Item 4</DropdownMenu.Item.Content>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <Tooltip.Popper w={120} aria-hidden={true}>
            <TooltipContent />
          </Tooltip.Popper>
        </Tooltip>
      </DropdownMenu.Menu>
    </DropdownMenu>
  );
};

export default Demo;
tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import { Flex } from 'intergalactic/flex-box';
import TrashM from 'intergalactic/icon/Trash/m';
import PlusM from 'intergalactic/icon/MathPlus/m';
import Button from 'intergalactic/button';
import ChevronRightIcon from '@semcore/icon/ChevronRight/m';

const Demo = () => {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>List item with actions</DropdownMenu.Trigger>
      <DropdownMenu.Menu>
        <DropdownMenu.Item>Item 1</DropdownMenu.Item>
        <DropdownMenu.Item>Item 2</DropdownMenu.Item>

        <DropdownMenu.Item>
          <DropdownMenu inlineActions placement={'right'}>
            <Flex justifyContent='space-between'>
              <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
                Item 3
              </DropdownMenu.Item.Content>
              <DropdownMenu.Actions gap={1}>
                <DropdownMenu.Item tag={Button} addonLeft={PlusM} aria-label={'Add new'} />
                <DropdownMenu.Item tag={Button} addonLeft={TrashM} aria-label={'Delete'} />
              </DropdownMenu.Actions>
            </Flex>
          </DropdownMenu>
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <DropdownMenu placement={'right'} interaction={'hover'} timeout={[0, 300]}>
            <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
              Item 4
              <ChevronRightIcon color='icon-secondary-neutral' />
            </DropdownMenu.Item.Content>
            <DropdownMenu.Menu>
              <DropdownMenu.Item>Add</DropdownMenu.Item>
              <DropdownMenu.Item>Delete</DropdownMenu.Item>
            </DropdownMenu.Menu>
          </DropdownMenu>
        </DropdownMenu.Item>
      </DropdownMenu.Menu>
    </DropdownMenu>
  );
};

export default Demo;

Render-function

Like with lower-level components, you can access the component's logic by passing a render function into the body.

You can find the list of available methods in the Context section of the API.

tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import Button from 'intergalactic/button';
import { Box } from '@semcore/flex-box';

const Demo = () => {
  return (
    <DropdownMenu>
      {(props, handlers) => {
        const {
          getTriggerProps, // encapsulates Trigger logic
          getPopperProps, // encapsulates Popper logic
          getListProps, // encapsulates List logic
          getItemProps, // // encapsulates Item logic
        } = props;

        const popperProps = getPopperProps();

        return (
          <React.Fragment>
            <Button {...getTriggerProps()}>Dropdown menu with render function</Button>
            <Box
              {...popperProps}
              hidden={!popperProps.visible}
              zIndex={10}
              p={2}
              style={{
                backgroundColor: 'var(--intergalactic-bg-primary-neutral, #FFF)',
                border: '1px solid gray',
              }}
            >
              <ul {...getListProps()}>
                <li {...getItemProps()}>Option 1</li>
                <li {...getItemProps()}>Option 2</li>
                <li {...getItemProps()}>Option 3</li>
                <li {...getItemProps()}>Option 4</li>
                <li {...getItemProps()}>Option 5</li>
              </ul>
              <button type='button' onClick={() => handlers.visible(false)}>
                Close me
              </button>
              <button type='button' onClick={() => handlers.highlightedIndex(2)}>
                Highlight item 3
              </button>
            </Box>
          </React.Fragment>
        );
      }}
    </DropdownMenu>
  );
};

export default Demo;

Nested menus

tsx
import React from 'react';
import DropdownMenu from 'intergalactic/dropdown-menu';
import Button from 'intergalactic/button';
import ChevronRightIcon from 'intergalactic/icon/ChevronRight/m';

const Demo = () => {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>Nested menus</DropdownMenu.Trigger>
      <DropdownMenu.Menu>
        <DropdownMenu.Item>Item 1</DropdownMenu.Item>
        <DropdownMenu.Item>Item 2</DropdownMenu.Item>
        <DropdownMenu.Item>Item 3</DropdownMenu.Item>
        <DropdownMenu.Item>
          <DropdownMenu placement='right' interaction='hover' timeout={[0, 300]}>
            <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
              Item 4
              <DropdownMenu.Item.Addon tag={ChevronRightIcon} color='icon-secondary-neutral' />
            </DropdownMenu.Item.Content>
            <DropdownMenu.Menu w={120}>
              <DropdownMenu.Item>
                <DropdownMenu placement='right' interaction='hover' timeout={[0, 300]}>
                  <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
                    Item 4.1
                    <DropdownMenu.Item.Addon
                      tag={ChevronRightIcon}
                      color='icon-secondary-neutral'
                    />
                  </DropdownMenu.Item.Content>
                  <DropdownMenu.Menu w={120}>
                    <DropdownMenu.Item>Item 4.1.1</DropdownMenu.Item>
                    <DropdownMenu.Item>Item 4.1.2</DropdownMenu.Item>
                    <DropdownMenu.Item>Item 4.1.3</DropdownMenu.Item>
                  </DropdownMenu.Menu>
                </DropdownMenu>
              </DropdownMenu.Item>
              <DropdownMenu.Item>
                <DropdownMenu placement='right' interaction='hover' timeout={[0, 300]}>
                  <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
                    Item 4.2
                    <DropdownMenu.Item.Addon
                      tag={ChevronRightIcon}
                      color='icon-secondary-neutral'
                    />
                  </DropdownMenu.Item.Content>
                  <DropdownMenu.Menu w={120}>
                    <DropdownMenu.Item>
                      <DropdownMenu placement='right' interaction='hover' timeout={[0, 300]}>
                        <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
                          Item 4.2.1
                          <DropdownMenu.Item.Addon
                            tag={ChevronRightIcon}
                            color='icon-secondary-neutral'
                          />
                        </DropdownMenu.Item.Content>
                        <DropdownMenu.Menu w={120}>
                          <DropdownMenu.Item>Item 4.2.1.1</DropdownMenu.Item>
                          <DropdownMenu.Item>Item 4.2.1.2</DropdownMenu.Item>
                          <DropdownMenu.Item>Item 4.2.1.3</DropdownMenu.Item>
                        </DropdownMenu.Menu>
                      </DropdownMenu>
                    </DropdownMenu.Item>
                    <DropdownMenu.Item>Item 4.2.2</DropdownMenu.Item>
                    <DropdownMenu.Item>Item 4.2.3</DropdownMenu.Item>
                  </DropdownMenu.Menu>
                </DropdownMenu>
              </DropdownMenu.Item>
              <DropdownMenu.Item>Item 4.3</DropdownMenu.Item>
            </DropdownMenu.Menu>
          </DropdownMenu>
        </DropdownMenu.Item>
        <DropdownMenu.Item>Item 5</DropdownMenu.Item>
      </DropdownMenu.Menu>
    </DropdownMenu>
  );
};

export default Demo;

Released under the MIT License.

Released under the MIT License.