Dropdown Menu

Displays a menu to the user—such as a set of actions or functions—triggered by a button. Supports submenus, checkable items, radio groups, and full keyboard navigation.

Preview Code
Interactive

Features

  • Can be controlled or uncontrolled.
  • Supports submenus with configurable reading direction.
  • Supports items, labels, groups of items.
  • Supports checkable items (single or multiple) with optional indeterminate state.
  • Supports modal and non-modal modes.
  • Customize side, alignment, offsets, collision handling.
  • Optionally render a pointing arrow.
  • Focus is fully managed.
  • Full keyboard navigation.
  • Typeahead support.
  • Dismissing and layering behavior is highly customizable.

Installation

Install the component from your command line.

Angular CLInpmpnpmyarnbun
Bash
ng g @ng-cn/core:c dropdown-menu

Anatomy

Import all parts and piece them together.

Typescript
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
  DropdownMenuPortal
} from '@/ui/dropdown-menu';
Html
<DropdownMenu>
  <DropdownMenuTrigger>Open</DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Action</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuLabel>Group Label</DropdownMenuLabel>
    <DropdownMenuGroup>
      <DropdownMenuItem>Sub Action</DropdownMenuItem>
    </DropdownMenuGroup>
    <DropdownMenuCheckboxItem checked={true}>
      Checkbox Item
    </DropdownMenuCheckboxItem>
    <DropdownMenuRadioGroup value="option1">
      <DropdownMenuRadioItem value="option1">
        Radio Option 1
      </DropdownMenuRadioItem>
    </DropdownMenuRadioGroup>
    <DropdownMenuSub>
      <DropdownMenuSubTrigger>Submenu</DropdownMenuSubTrigger>
      <DropdownMenuPortal>
        <DropdownMenuSubContent>
          <DropdownMenuItem>Sub Item</DropdownMenuItem>
        </DropdownMenuSubContent>
      </DropdownMenuPortal>
    </DropdownMenuSub>
  </DropdownMenuContent>
</DropdownMenu>

API Reference

Root

Contains all the parts of a dropdown menu.

PropTypeDefault
open boolean
The controlled open state of the dropdown menu. Use with (openChange).
defaultOpen boolean false
The open state when the dropdown menu is initially rendered. Use when you do not need to control its open state.
modal boolean true
When true, the menu operates in modal mode (prevents interaction outside). When false, allows interaction with other elements.
dir 'ltr' | 'rtl'
The reading direction of the dropdown menu when its content overflows. Defaults to inheriting from the document.

Data Attributes

AttributeValues
[data-state]"open" | "closed"

Trigger

The button that toggles the dropdown menu. By default, the DropdownMenuContent will position itself against the trigger.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.

Data Attributes

AttributeValues
[data-state]"open" | "closed"
[data-disabled]Present when disabled

Portal

When used, portals the content part into the body. This is useful for avoiding stacking context issues.

PropTypeDefault
forceMount boolean
Used to force mounting when more control is needed.
container HTMLElement document.body
The element to portal into. Defaults to the body.

Content

The component that pops out when the dropdown menu is open.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
loop boolean false
When true, keyboard navigation will loop from last item to first, and vice versa.
side 'top' | 'right' | 'bottom' | 'left' 'bottom'
The preferred side of the trigger to render against.
sideOffset number 0
The distance in pixels from the trigger to the content.
align 'start' | 'center' | 'end' 'start'
The preferred alignment against the trigger. May change when collisions occur.
alignOffset number 0
An offset in pixels from the start or end alignment option.
avoidCollisions boolean true
When true, overrides the side and align preferences to avoid collisions with boundary edges.
collisionBoundary Element | null | Element[] []
Element used to calculate available space. By default uses the viewport. Pass null to disable boundary checks.
collisionPadding number | Partial<Record<Side, number>> 0
The distance in pixels from the boundary edges where collision detection should occur.
arrowPadding number 0
The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners.
sticky 'partial' | 'always' 'partial'
The sticky behavior of the content. "partial" will keep the content in the viewport as the user scrolls.
hideWhenDetached boolean false
When true, hides the content when the trigger becomes hidden.
forceMount boolean
Used to force mounting when more control is needed.
class string
Additional CSS classes to apply to the content.

Data Attributes

AttributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
[data-orientation]"vertical" | "horizontal"

CSS Variables

VariableDescription
--radix-dropdown-menu-content-transform-originThe transform-origin computed from the content and arrow positions/offsets.
--radix-dropdown-menu-content-available-widthThe remaining width between the trigger and the boundary edge.
--radix-dropdown-menu-content-available-heightThe remaining height between the trigger and the boundary edge.
--radix-dropdown-menu-trigger-widthThe width of the trigger.
--radix-dropdown-menu-trigger-heightThe height of the trigger.

Item

The component that contains the dropdown menu items.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
disabled boolean
When true, prevents the user from interacting with this item.
onSelect (event: Event) => void
Callback fired when the user selects the item.
textValue string
Optional text used for typeahead purposes. By default the typeahead will use the .textContent of the item.
class string
Additional CSS classes to apply to the item.

Data Attributes

AttributeValues
[data-orientation]"vertical" | "horizontal"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

Group

Used to group multiple DropdownMenuItems.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.

Label

Used to render a label. It won't be focusable using arrow keys. Can be used to describe a group of items.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.

CheckboxItem

An item that can be controlled and rendered like a checkbox.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
checked boolean | 'indeterminate'
The controlled checked state of the checkbox item.
onCheckedChange (checked: boolean | 'indeterminate') => void
Callback fired when the checked state changes.
disabled boolean
When true, prevents the user from interacting with this item.
onSelect (event: Event) => void
Callback fired when the user selects the item.
textValue string
Optional text used for typeahead purposes.

Data Attributes

AttributeValues
[data-state]"checked" | "unchecked" | "indeterminate"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

RadioGroup

Used to group multiple DropdownMenuRadioItems.

PropTypeDefault
value string
The value of the selected radio item.
onValueChange (value: string) => void
Callback fired when the value changes.
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.

RadioItem

An item that can be controlled and rendered like a radio.

PropTypeDefault
value *string
The value of the radio item.
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
disabled boolean
When true, prevents the user from interacting with this item.
onSelect (event: Event) => void
Callback fired when the user selects the item.
textValue string
Optional text used for typeahead purposes.

Data Attributes

AttributeValues
[data-state]"checked" | "unchecked"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

ItemIndicator

Renders when the parent DropdownMenuCheckboxItem or DropdownMenuRadioItem is checked. Can be used as a wrapper for an icon.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
forceMount boolean
Used to force mounting when more control is needed.

Data Attributes

AttributeValues
[data-state]"checked" | "unchecked" | "indeterminate"

Separator

Used to visually separate items in the dropdown menu.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.

Shortcut

Used to render keyboard shortcut hints alongside menu items.

PropTypeDefault
class string
Additional CSS classes to apply to the shortcut.

Sub

Contains all the parts of a submenu.

PropTypeDefault
open boolean
The controlled open state of the submenu.
defaultOpen boolean
The open state when the submenu is initially rendered. Use when you do not need to control its open state.
onOpenChange (open: boolean) => void
Callback fired when the open state changes.

SubTrigger

An item that opens a submenu. Must be rendered inside DropdownMenuSub.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
disabled boolean
When true, prevents the user from interacting with this item.
textValue string
Optional text used for typeahead purposes.

Data Attributes

AttributeValues
[data-state]"open" | "closed"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

SubContent

The component that pops out when a submenu is open. Must be rendered inside DropdownMenuSub.

PropTypeDefault
asChild boolean false
Change the default rendered element for the one passed as a child, merging their props and behavior.
loop boolean false
When true, keyboard navigation will loop from last item to first, and vice versa.
sideOffset number 0
The distance in pixels from the trigger to the content.
alignOffset number 0
An offset in pixels from the start or end alignment option.
avoidCollisions boolean true
When true, overrides the side and align preferences to avoid collisions.
collisionBoundary Element | null | Element[] []
Element used to calculate available space.
collisionPadding number | Partial<Record<Side, number>> 0
The distance in pixels from the boundary edges where collision detection should occur.
arrowPadding number 0
The padding between the arrow and the edges of the content.
sticky 'partial' | 'always' 'partial'
The sticky behavior of the content.
hideWhenDetached boolean false
When true, hides the content when the trigger becomes hidden.
forceMount boolean
Used to force mounting when more control is needed.

Data Attributes

AttributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
[data-orientation]"vertical" | "horizontal"

Examples

Basic

A simple dropdown menu with basic items and actions.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';

export function DropdownMenuDemo() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56" align="start">
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Profile</DropdownMenuItem>
        <DropdownMenuItem>Billing</DropdownMenuItem>
        <DropdownMenuItem>Team</DropdownMenuItem>
        <DropdownMenuItem>Subscription</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

With Checkboxes

Use checkbox items for toggling options on and off.

Html
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';
import { signal } from '@angular/core';
import { CheckIcon } from 'lucide-angular';

export function DropdownMenuCheckboxes() {
  const showStatusBar = signal(true);
  const showActivityBar = signal(false);
  const showPanel = signal(false);

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>Appearance</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuCheckboxItem
          checked={showStatusBar()}
          onCheckedChange={() => showStatusBar.update(v => !v)}
        >
          Status Bar
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={showActivityBar()}
          onCheckedChange={() => showActivityBar.update(v => !v)}
          disabled
        >
          Activity Bar
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={showPanel()}
          onCheckedChange={() => showPanel.update(v => !v)}
        >
          Panel
        </DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

With Radio Group

Use radio items for mutually exclusive options.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';
import { signal } from '@angular/core';

export function DropdownMenuRadioGroupDemo() {
  const position = signal('bottom');

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>Panel Position</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuRadioGroup value={position()} onValueChange={(val) => position.set(val)}>
          <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

With Submenus

Create nested submenus with the Sub component.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';
import { ChevronRightIcon } from 'lucide-angular';

export function DropdownMenuSubmenus() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56" align="start">
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Profile</DropdownMenuItem>
        <DropdownMenuSub>
          <DropdownMenuSubTrigger>
            More Tools
            <ChevronRightIcon className="ml-auto h-4 w-4" />
          </DropdownMenuSubTrigger>
          <DropdownMenuPortal>
            <DropdownMenuSubContent sideOffset={-4}>
              <DropdownMenuItem>Save Page As...</DropdownMenuItem>
              <DropdownMenuItem>Create Shortcut...</DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem>Developer Tools</DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuPortal>
        </DropdownMenuSub>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Log out</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

With Disabled Items

Disable items to prevent user interaction.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';

export function DropdownMenuDisabled() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-48">
        <DropdownMenuItem>Edit</DropdownMenuItem>
        <DropdownMenuItem>Duplicate</DropdownMenuItem>
        <DropdownMenuItem disabled>Archive</DropdownMenuItem>
        <DropdownMenuItem>Delete</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

With Shortcuts

Display keyboard shortcuts alongside menu items.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';

export function DropdownMenuShortcuts() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56" align="start">
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuGroup>
          <DropdownMenuItem>
            Profile
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Billing
            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Settings
            <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Keyboard shortcuts
            <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

With Complex Items

Render rich content inside menu items.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/ui/avatar';
import { NgFor } from '@angular/common';

export function DropdownMenuComplex() {
  const users = [
    { name: 'Adolfo Hess', avatar: 'AH' },
    { name: 'Miyah Myles', avatar: 'MM' },
  ];

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <NgFor [ngForOf]="users">
          <DropdownMenuItem className="gap-2">
            <Avatar className="h-6 w-6">
              <AvatarImage src="..." />
              <AvatarFallback>{{ user.avatar }}</AvatarFallback>
            </Avatar>
            {{ user.name }}
          </DropdownMenuItem>
        </NgFor>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Controlling Position

Use side, align, and sideOffset to control content positioning.

Html
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';

export function DropdownMenuPositioning() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent
        side="right"
        align="end"
        sideOffset={8}
        className="w-48"
      >
        <DropdownMenuItem>New Tab</DropdownMenuItem>
        <DropdownMenuItem>New Window</DropdownMenuItem>
        <DropdownMenuItem>New Private Window</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Origin-Aware Animations

Use CSS custom properties to create animations that originate from the correct position.

Html
// Component
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/ui/dropdown-menu';
import { Button } from '@/ui/button';

export function DropdownMenuAnimated() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-48 animate-in fade-in-0 zoom-in-95" sideOffset={5}>
        <DropdownMenuItem>New Tab</DropdownMenuItem>
        <DropdownMenuItem>New Window</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

// Tailwind config for custom animation
export const tailwindConfig = {
  theme: {
    extend: {
      animation: {
        'content-show': 'content-show 0.16s cubic-bezier(0.16, 1, 0.3, 1)',
      },
      keyframes: {
        'content-show': {
          from: {
            opacity: '0',
            transform: 'scale(0.96) translate(0, -2px)',
          },
          to: {
            opacity: '1',
            transform: 'scale(1)',
          },
        },
      },
    },
  },
};
Css
/* Using CSS custom properties for origin-aware animations */
[data-radix-dropdown-menu-content] {
  transform-origin: var(--radix-dropdown-menu-content-transform-origin);
  animation: scaleIn 0.2s ease-out;
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* Collision-aware animations */
[data-radix-dropdown-menu-content][data-side="top"] {
  animation: slideDown 0.2s ease-out;
}

[data-radix-dropdown-menu-content][data-side="bottom"] {
  animation: slideUp 0.2s ease-out;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(2px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-2px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Accessibility

Adheres to the Menu Button WAI-ARIA design pattern .

Keyboard Interactions

KeyDescription
Space When focus is on DropdownMenuTrigger, opens the dropdown menu and focuses the first item. When focus is on an item, activates the focused item.
Enter When focus is on DropdownMenuTrigger, opens the dropdown menu and focuses the first item. When focus is on an item, activates the focused item.
ArrowDown When focus is on DropdownMenuTrigger, opens the dropdown menu. When focus is on an item, moves focus to the next item.
ArrowUp When focus is on an item, moves focus to the previous item.
ArrowRight When focus is on DropdownMenuSubTrigger, opens or closes the submenu depending on reading direction.
ArrowLeft When focus is on DropdownMenuSubTrigger, opens or closes the submenu depending on reading direction.
Esc Closes the dropdown menu and moves focus to DropdownMenuTrigger.
Tab Moves focus to the next focusable element. Does not move focus within the dropdown menu items.
Typeahead When focus is within the dropdown menu, typing a character moves focus to the next menu item that starts with that character.