Skip to main content

Dropdown Menu

Dropdown Menu is an overlay that lists actions behind a button, such as editing, duplicating, or archiving a project.

Import

import { DropdownMenu } from '@vortexlabs/vortex';
import type { DropdownMenuProps } from '@vortexlabs/vortex';

Allied components

Other links

"use client";

import { Button, DropdownMenu } from "@vortexlabs/vortex";
import { DotsHorizontal } from "@vortexlabs/vortex-icons";

export const DropdownMenuExample = () => {
  return (
    <DropdownMenu width="240px">
      <DropdownMenu.Trigger>
        {(attributes) => (
          <Button
            variant="secondary"
            onlyIcon={DotsHorizontal}
            ariaLabel="Project actions"
            attributes={attributes}
          />
        )}
      </DropdownMenu.Trigger>
      <DropdownMenu.Content>
        <DropdownMenu.Item onClick={() => {}}>Edit</DropdownMenu.Item>
        <DropdownMenu.Item onClick={() => {}}>Duplicate</DropdownMenu.Item>
        <DropdownMenu.Item onClick={() => {}}>Archive</DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
};

Features

  • Opens on click or hover.
  • Supports icons, shortcuts, and destructive items.
  • Supports labeled sections and nested submenus.
  • Closes after an item is selected.

Anatomy

<DropdownMenu>
<DropdownMenu.Trigger>
  {(attributes) => <Button attributes={attributes}>Actions</Button>}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
  <DropdownMenu.Item>Edit</DropdownMenu.Item>
  <DropdownMenu.Item>Duplicate</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
Component Part
Description
Holds the open state and positions the menu.
Gives your button the attributes that open the menu.
Holds the menu items.
Runs one action and closes the menu.
Groups related items.
Names a section.
Holds a nested menu that opens from its SubTrigger.
Opens the submenu on hover or with the arrow key.

Icons and Shortcuts

startIcon adds an icon, and shortcut shows a key hint. The hint is only a label; register the key yourself.

import { BoxArchive, Clone, PenLine } from "@vortexlabs/vortex-icons";

<DropdownMenu width="240px">
  <DropdownMenu.Trigger>
    {(attributes) => (
      <Button variant="secondary" attributes={attributes}>
        Actions
      </Button>
    )}
  </DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Item startIcon={PenLine} shortcut="⌘E" onClick={() => {}}>
      Edit
    </DropdownMenu.Item>
    <DropdownMenu.Item startIcon={Clone} shortcut="⌘D" onClick={() => {}}>
      Duplicate
    </DropdownMenu.Item>
    <DropdownMenu.Item startIcon={BoxArchive} onClick={() => {}}>
      Archive
    </DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu>

Sections

Section groups items, and an optional SectionLabel names the group. tone="critical" marks destructive actions.

<DropdownMenu width="240px">
  <DropdownMenu.Trigger>
    {(attributes) => (
      <Button variant="secondary" attributes={attributes}>
        Document
      </Button>
    )}
  </DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Section>
      <DropdownMenu.SectionLabel>Document</DropdownMenu.SectionLabel>
      <DropdownMenu.Item startIcon={PenLine} onClick={() => {}}>
        Edit
      </DropdownMenu.Item>
      <DropdownMenu.Item startIcon={Clone} onClick={() => {}}>
        Duplicate
      </DropdownMenu.Item>
    </DropdownMenu.Section>
    <DropdownMenu.Section>
      <DropdownMenu.Item startIcon={BoxArchive} onClick={() => {}}>
        Archive
      </DropdownMenu.Item>
      <DropdownMenu.Item tone="critical" startIcon={Trash} shortcut="⌘⌫" onClick={() => {}}>
        Delete
      </DropdownMenu.Item>
    </DropdownMenu.Section>
  </DropdownMenu.Content>
</DropdownMenu>

Wrap a SubTrigger and a nested Content in SubMenu. It opens on hover or with the arrow key, and choosing an item closes every menu.

import { Link, Mail, PenLine, Share, Trash } from "@vortexlabs/vortex-icons";

<DropdownMenu width="240px">
  <DropdownMenu.Trigger>
    {(attributes) => (
      <Button variant="secondary" attributes={attributes}>
        File
      </Button>
    )}
  </DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Item startIcon={PenLine} onClick={() => {}}>
      Rename
    </DropdownMenu.Item>
    <DropdownMenu.SubMenu width="200px">
      <DropdownMenu.SubTrigger startIcon={Share}>Share</DropdownMenu.SubTrigger>
      <DropdownMenu.Content>
        <DropdownMenu.Item startIcon={Link} onClick={() => {}}>
          Copy link
        </DropdownMenu.Item>
        <DropdownMenu.Item startIcon={Mail} onClick={() => {}}>
          Email
        </DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu.SubMenu>
    <DropdownMenu.Item tone="critical" startIcon={Trash} onClick={() => {}}>
      Delete
    </DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu>

Disabled Items

disabled blocks an Item or SubTrigger, and keyboard navigation skips it.

<DropdownMenu width="240px">
  <DropdownMenu.Trigger>
    {(attributes) => (
      <Button variant="secondary" attributes={attributes}>
        Actions
      </Button>
    )}
  </DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Item startIcon={PenLine} onClick={() => {}}>
      Edit
    </DropdownMenu.Item>
    <DropdownMenu.Item disabled startIcon={Clone} onClick={() => {}}>
      Duplicate
    </DropdownMenu.Item>
    <DropdownMenu.Item disabled tone="critical" startIcon={Trash} onClick={() => {}}>
      Delete
    </DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu>

Controlled State

Control visibility with active, onOpen, and onClose. onClose receives the reason, like item-selection.

Sorted by: none
"use client";

import { Button, DropdownMenu, Stack, Text } from "@vortexlabs/vortex";
import { useState } from "react";

export const DropdownMenuControlledExample = () => {
  const [open, setOpen] = useState(false);
  const [lastAction, setLastAction] = useState("none");

  return (
    <Stack gap={3} align="start">
      <DropdownMenu
        width="240px"
        active={open}
        onOpen={() => setOpen(true)}
        onClose={() => setOpen(false)}
      >
        <DropdownMenu.Trigger>
          {(attributes) => (
            <Button variant="secondary" attributes={attributes}>
              Sort tasks
            </Button>
          )}
        </DropdownMenu.Trigger>
        <DropdownMenu.Content>
          <DropdownMenu.Item onClick={() => setLastAction("Due date")}>
            Due date
          </DropdownMenu.Item>
          <DropdownMenu.Item onClick={() => setLastAction("Priority")}>
            Priority
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu>
      <Text size="body-sm-desktop">Sorted by: {lastAction}</Text>
    </Stack>
  );
};

Use Select to pick a value, and ContextMenu for right-click actions.

Accessibility

Key
Description
EnterSpace
Opens the menu, or selects the focused item.
Down arrowUp arrow
Moves to the next or previous item, wrapping at the ends.
Right arrow
Opens a submenu (ArrowLeft in right-to-left layouts).
Left arrow
Closes a submenu (ArrowRight in right-to-left layouts).
Escape
Closes the menu and refocuses the trigger.
Give an icon-only trigger an ariaLabel; the menu uses it as its name.