Skip to main content
Gremorie

Dropdown Menu

Verb-led action menu attached to a trigger. Items, groups, separators, checkbox or radio items, submenus, and right-aligned shortcut hints.

Overview

DropdownMenu is the right primitive for actions: Edit, Delete, Duplicate, Open in new tab. It is distinct from Select, which holds values (USA, Brazil, Japan). When the items in the menu read like verbs, this is the right primitive.

The compound exposes everything Radix offers: items with destructive variant, checkbox and radio items with auto-rendered indicators, grouped sections, separators, submenus, and a shortcut helper that right-aligns text.

Preview

'use client';import { Button } from '@gremorie/rx-forms';import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuLabel,  DropdownMenuSeparator,  DropdownMenuTrigger,} from '@gremorie/rx-overlays';export function DropdownMenuPreview() {  return (    <DropdownMenu>      <DropdownMenuTrigger asChild>        <Button variant="outline">Actions</Button>      </DropdownMenuTrigger>      <DropdownMenuContent>        <DropdownMenuLabel>Actions</DropdownMenuLabel>        <DropdownMenuSeparator />        <DropdownMenuItem>Open in chat</DropdownMenuItem>        <DropdownMenuItem>Copy URL</DropdownMenuItem>        <DropdownMenuItem>Export</DropdownMenuItem>      </DropdownMenuContent>    </DropdownMenu>  );}

Anatomy

DropdownMenu                       Root holding open state
├─ DropdownMenuTrigger             Element that opens the menu
└─ DropdownMenuContent             Portalled surface (sideOffset default 4)
   ├─ DropdownMenuLabel            Non-interactive section label
   ├─ DropdownMenuSeparator        Divider
   ├─ DropdownMenuGroup            Groups related items
   │  └─ DropdownMenuItem          Action row (variant default | destructive, inset)
   ├─ DropdownMenuCheckboxItem     Toggleable item
   ├─ DropdownMenuRadioGroup       Single-choice container
   │  └─ DropdownMenuRadioItem     Single-choice item
   ├─ DropdownMenuSub              Nested submenu
   │  ├─ DropdownMenuSubTrigger    Opens the submenu
   │  └─ DropdownMenuSubContent    Submenu surface
   └─ DropdownMenuShortcut         Right-aligned keyboard hint

Installation

bash npx gremorie@latest add rx-dropdown-menu

bash pnpm dlx gremorie@latest add rx-dropdown-menu

bash yarn dlx gremorie@latest add rx-dropdown-menu

bash bunx --bun gremorie@latest add rx-dropdown-menu

Usage

import { Button } from "@gremorie/rx-forms";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@gremorie/rx-overlays";

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Actions</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>Actions</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Open in chat</DropdownMenuItem>
        <DropdownMenuItem>Copy URL</DropdownMenuItem>
        <DropdownMenuItem>Export</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

The Angular edition of this component ships from source today (see the workbench for the side-by-side); its registry entry is coming next.

API

Extends Radix DropdownMenu.Root. Standard controlled API (open, defaultOpen, onOpenChange, modal).

Extends Radix DropdownMenu.Trigger. Use asChild to forward styles to a Button or any custom trigger.

PropTypeDefaultDescription
sideOffsetnumber4Pixel gap between trigger and content.
side"top" | "right" | "bottom" | "left""bottom"Preferred side; flips automatically.
align"start" | "center" | "end""center"Alignment relative to the trigger axis.

Wrapped in a Radix Portal. Capped at --radix-dropdown-menu-content-available-height, scrollable, min-w-[8rem].

PropTypeDefaultDescription
insetbooleanfalseAdds pl-8 so the item lines up with checkbox/radio rows.
variant"default" | "destructive""default"destructive switches text and icon color to the destructive token.
disabledbooleanfalseDisables interaction; renders at 50% opacity.
PropTypeDefaultDescription
checkedboolean | "indeterminate"-Controlled checked state.
onCheckedChange(checked: boolean) => void-Fired when toggled.

Renders an absolute-positioned check icon on the left when checked.

Pair a group with one or more radio items. The group owns the value and onValueChange. Each radio item renders an auto-positioned bullet when selected.

Non-interactive section heading.

PropTypeDefaultDescription
insetbooleanfalseAdds pl-8 for alignment with check/radio rows.

Thin bg-border divider with negative margin so it spans the menu padding.

A right-aligned <span> for keyboard hints (⌘C, ⇧⌫). Pure layout helper; no Radix logic.

Nested submenu. Sub-trigger renders an auto-positioned chevron on the right. Sub-content opens to the side of the trigger.

Pass-throughs over the Radix primitives with data-slot attributes.

Composition

  1. <DropdownMenu> owns the open/close state.
  2. <DropdownMenuTrigger asChild> wraps a Button or icon button.
  3. <DropdownMenuContent> mounts via Portal anchored to the trigger.
  4. Inside content: group items with DropdownMenuGroup, label sections with DropdownMenuLabel, divide with DropdownMenuSeparator.
  5. Shortcuts sit at the right edge via DropdownMenuShortcut.
  6. Submenus wrap nested DropdownMenuSub blocks.

Variations

The canonical row-actions menu: right-aligned shortcuts, a nested DropdownMenuSub, and the destructive variant for delete.

'use client';import {  CopyIcon,  MoreHorizontalIcon,  PencilIcon,  Trash2Icon,} from 'lucide-react';import { Button } from '@gremorie/rx-forms';import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuSeparator,  DropdownMenuShortcut,  DropdownMenuSub,  DropdownMenuSubContent,  DropdownMenuSubTrigger,  DropdownMenuTrigger,} from '@gremorie/rx-overlays';export function DropdownMenuSubmenuPreview() {  return (    <DropdownMenu>      <DropdownMenuTrigger asChild>        <Button variant="ghost" size="icon" aria-label="Row actions">          <MoreHorizontalIcon />        </Button>      </DropdownMenuTrigger>      <DropdownMenuContent align="end" className="w-48">        <DropdownMenuItem>          <PencilIcon /> Edit          <DropdownMenuShortcut>⌘E</DropdownMenuShortcut>        </DropdownMenuItem>        <DropdownMenuItem>          <CopyIcon /> Duplicate          <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>        </DropdownMenuItem>        <DropdownMenuSub>          <DropdownMenuSubTrigger>Share</DropdownMenuSubTrigger>          <DropdownMenuSubContent>            <DropdownMenuItem>Copy link</DropdownMenuItem>            <DropdownMenuItem>Email</DropdownMenuItem>            <DropdownMenuItem>Embed</DropdownMenuItem>          </DropdownMenuSubContent>        </DropdownMenuSub>        <DropdownMenuSeparator />        <DropdownMenuItem variant="destructive">          <Trash2Icon /> Delete          <DropdownMenuShortcut>⌫</DropdownMenuShortcut>        </DropdownMenuItem>      </DropdownMenuContent>    </DropdownMenu>  );}

Checkbox items

Toggle visible columns or filters without closing the menu. Each DropdownMenuCheckboxItem renders its own check indicator.

'use client';import { useState } from 'react';import { Button } from '@gremorie/rx-forms';import {  DropdownMenu,  DropdownMenuCheckboxItem,  DropdownMenuContent,  DropdownMenuLabel,  DropdownMenuSeparator,  DropdownMenuTrigger,} from '@gremorie/rx-overlays';export function DropdownMenuCheckboxesPreview() {  const [columns, setColumns] = useState({    name: true,    email: true,    role: false,  });  return (    <DropdownMenu>      <DropdownMenuTrigger asChild>        <Button variant="outline">Toggle columns</Button>      </DropdownMenuTrigger>      <DropdownMenuContent className="w-48">        <DropdownMenuLabel>Visible columns</DropdownMenuLabel>        <DropdownMenuSeparator />        <DropdownMenuCheckboxItem          checked={columns.name}          onCheckedChange={(checked) =>            setColumns((prev) => ({ ...prev, name: checked === true }))          }        >          Name        </DropdownMenuCheckboxItem>        <DropdownMenuCheckboxItem          checked={columns.email}          onCheckedChange={(checked) =>            setColumns((prev) => ({ ...prev, email: checked === true }))          }        >          Email        </DropdownMenuCheckboxItem>        <DropdownMenuCheckboxItem          checked={columns.role}          onCheckedChange={(checked) =>            setColumns((prev) => ({ ...prev, role: checked === true }))          }        >          Role        </DropdownMenuCheckboxItem>      </DropdownMenuContent>    </DropdownMenu>  );}

Radio items

Pick a single value from a DropdownMenuRadioGroup. The selected item renders an auto-positioned bullet.

'use client';import { useState } from 'react';import { Button } from '@gremorie/rx-forms';import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuLabel,  DropdownMenuRadioGroup,  DropdownMenuRadioItem,  DropdownMenuSeparator,  DropdownMenuTrigger,} from '@gremorie/rx-overlays';export function DropdownMenuRadioPreview() {  const [position, setPosition] = useState('bottom');  return (    <DropdownMenu>      <DropdownMenuTrigger asChild>        <Button variant="outline">Panel position</Button>      </DropdownMenuTrigger>      <DropdownMenuContent className="w-48">        <DropdownMenuLabel>Panel position</DropdownMenuLabel>        <DropdownMenuSeparator />        <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>          <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>          <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>          <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>        </DropdownMenuRadioGroup>      </DropdownMenuContent>    </DropdownMenu>  );}

Items with shortcuts and destructive action

The canonical row-actions menu. Note the right-aligned shortcuts and the destructive variant for delete.

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="ghost" size="icon" aria-label="Row actions">
      <MoreHorizontalIcon />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <DropdownMenuItem>
      <PencilIcon /> Edit
      <DropdownMenuShortcut>⌘E</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuItem>
      <CopyIcon /> Duplicate
      <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive">
      <Trash2Icon /> Delete
      <DropdownMenuShortcut>⌫</DropdownMenuShortcut>
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Checkbox items

Toggle visible columns or filters without closing the menu.

<DropdownMenuContent>
  <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
  <DropdownMenuSeparator />
  <DropdownMenuCheckboxItem
    checked={cols.name}
    onCheckedChange={toggle('name')}
  >
    Name
  </DropdownMenuCheckboxItem>
  <DropdownMenuCheckboxItem
    checked={cols.email}
    onCheckedChange={toggle('email')}
  >
    Email
  </DropdownMenuCheckboxItem>
</DropdownMenuContent>

Nest secondary actions inside a sub-trigger.

<DropdownMenuContent>
  <DropdownMenuItem>Open</DropdownMenuItem>
  <DropdownMenuSub>
    <DropdownMenuSubTrigger>Share</DropdownMenuSubTrigger>
    <DropdownMenuSubContent>
      <DropdownMenuItem>Copy link</DropdownMenuItem>
      <DropdownMenuItem>Email</DropdownMenuItem>
      <DropdownMenuItem>Embed</DropdownMenuItem>
    </DropdownMenuSubContent>
  </DropdownMenuSub>
</DropdownMenuContent>

Accessibility

  • Role: role="menu" on content, role="menuitem" on items, role="menuitemcheckbox" and role="menuitemradio" on stateful variants.
  • Keyboard: arrow keys navigate; Enter and Space activate; type-ahead jumps to items starting with the typed letter; Esc closes; Tab closes and continues page focus.
  • Submenus: opens, closes.
  • Focus management: focus moves into the menu on open and returns to the trigger on close.
  • aria-expanded is set on the trigger automatically; aria-controls links to the content id.
  • Destructive variant: visual cue only; pair with confirmation (AlertDialog) if the action is irreversible.
  • Reduced motion: open/close animations honor prefers-reduced-motion.
  • Context Menu - same compound, triggered by right-click on a region.
  • Select - value-led picker (countries, status), not actions.
  • Popover - generic anchored overlay when items don't follow menu semantics.
  • Command - searchable command list for power users.

On this page