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 '@semcore/dropdown-menu';
import Button from '@semcore/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

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 '@semcore/dropdown-menu';
import Button from '@semcore/button';
import { Text } from '@semcore/typography';
import { Flex } from '@semcore/flex-box';

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

export default Demo;

Second 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 '@semcore/dropdown-menu';
import Button from '@semcore/button';
import Link from '@semcore/link';
import { Text } from '@semcore/typography';
import Notice from '@semcore/notice';
import SpinContainer from '@semcore/spin-container';
import FileExportM from '@semcore/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' aria-label={'Export options'}>
        <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;

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.Addon: Used to add, for example, icons.
  • DropdownMenu.Item.Text: Used for wrapping text if used with addons.
  • 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 '@semcore/dropdown-menu';
import Button from '@semcore/button';
import Tooltip from '@semcore/tooltip';
import DesktopIconM from '@semcore/icon/Desktop/m';

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

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

const Demo = () => {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>Explore menu item types</DropdownMenu.Trigger>
      <DropdownMenu.Menu>
        <Tooltip placement={'right'} timeout={[0, 50]}>
          <DropdownMenu.Group title={'Menu title'} subTitle={'Subtitle'}>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Menu item 1</DropdownMenu.Item.Content>
            </DropdownMenu.Item>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Menu item 2</DropdownMenu.Item.Content>
              <DropdownMenu.Item.Hint>Hint for menu item 2</DropdownMenu.Item.Hint>
            </DropdownMenu.Item>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>
                <DropdownMenu.Item.Addon>
                  <DesktopIconM />
                </DropdownMenu.Item.Addon>
                <DropdownMenu.Item.Text>Menu item 3</DropdownMenu.Item.Text>
              </DropdownMenu.Item.Content>
              <DropdownMenu.Item.Hint>Hint for menu item 3</DropdownMenu.Item.Hint>
            </DropdownMenu.Item>
            <DropdownMenu.Item tag={Tooltip.Trigger}>
              <DropdownMenu.Item.Content>Menu 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 '@semcore/dropdown-menu';
import { Flex } from '@semcore/flex-box';
import TrashM from '@semcore/icon/Trash/m';
import PlusM from '@semcore/icon/MathPlus/m';
import Button from '@semcore/button';
import ChevronRightIcon from '@semcore/icon/ChevronRight/m';

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

        <DropdownMenu.Item>
          <DropdownMenu inlineActions placement={'right'}>
            <Flex justifyContent='space-between'>
              <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
                Menu item 3
              </DropdownMenu.Item.Content>
              <DropdownMenu.Actions gap={1}>
                <DropdownMenu.Item tag={Button} addonLeft={PlusM} title={'Add new'} />
                <DropdownMenu.Item tag={Button} addonLeft={TrashM} title={'Delete'} />
              </DropdownMenu.Actions>
            </Flex>
          </DropdownMenu>
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <DropdownMenu
            placement={'right-start'}
            interaction={DropdownMenu.nestedMenuInteraction}
            timeout={[0, 300]}
            offset={[-11, 12]}
          >
            <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
              Menu 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;

Nested menus

tsx
import React from 'react';
import DropdownMenu from '@semcore/dropdown-menu';
import Button from '@semcore/button';
import ChevronRightIcon from '@semcore/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-start'
            interaction={DropdownMenu.nestedMenuInteraction}
            timeout={[0, 300]}
            offset={[-11, 12]}
          >
            <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-start'
                  interaction={DropdownMenu.nestedMenuInteraction}
                  timeout={[0, 300]}
                  offset={[-11, 12]}
                >
                  <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-start'
                  interaction={DropdownMenu.nestedMenuInteraction}
                  timeout={[0, 300]}
                  offset={[-11, 12]}
                >
                  <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-start'
                        interaction={DropdownMenu.nestedMenuInteraction}
                        timeout={[0, 300]}
                        offset={[-11, 12]}
                      >
                        <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;

Nested menus with focusable elements

tsx
import React from 'react';
import DropdownMenu from '@semcore/dropdown-menu';
import ChevronRightIcon from '@semcore/icon/ChevronRight/m';
import InputNumber from '@semcore/input-number';
import NeighborLocation from '@semcore/neighbor-location';
import Divider from '@semcore/divider';
import { Box } from '@semcore/flex-box';
import Button from '@semcore/button';

const options = ['Item 1', 'Item 2', 'Item 3'];
const min = 1;
const max = 8;

const Demo = () => {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger tag={Button}>Click me</DropdownMenu.Trigger>
      <DropdownMenu.Menu>
        {options.map((item) => {
          return (
            <DropdownMenu.Item key={item}>
              <DropdownMenu
                placement='right-start'
                interaction={DropdownMenu.nestedMenuInteraction}
                timeout={[0, 300]}
                offset={[-11, 12]}
              >
                <DropdownMenu.Item.Content tag={DropdownMenu.Trigger}>
                  {item}
                  <DropdownMenu.Item.Addon tag={ChevronRightIcon} color='icon-secondary-neutral' />
                </DropdownMenu.Item.Content>
                <DropdownMenu.Popper w={150} aria-label={'Some item 4 options and controls'}>
                  <DropdownMenu.List>
                    <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.List>
                  <Divider my={1} />
                  <Box p={2}>
                    <NeighborLocation>
                      <InputNumber w='50%'>
                        <InputNumber.Value min={min} max={max} placeholder={min.toString()} />
                        <InputNumber.Controls />
                      </InputNumber>
                      <InputNumber w='50%'>
                        <InputNumber.Value min={min} max={max} placeholder={max.toString()} />
                        <InputNumber.Controls />
                      </InputNumber>
                    </NeighborLocation>
                    <Button w='100%' mt={1} use='primary'>
                      Apply
                    </Button>
                  </Box>
                </DropdownMenu.Popper>
              </DropdownMenu>
            </DropdownMenu.Item>
          );
        })}
      </DropdownMenu.Menu>
    </DropdownMenu>
  );
};

export default Demo;

export const App = () => <Demo />;

Released under the MIT License.

Released under the MIT License.