nori-ui

DropdownMenu

Click-triggered floating menu anchored to a trigger element — for actions, navigation, and destructive operations.

5.3 kBgzipped

At a glance

  • Compose: DropdownMenu, DropdownMenu.Trigger, DropdownMenu.Content, DropdownMenu.Item, DropdownMenu.Separator, DropdownMenu.Label.
  • Trigger uses asChild — wrap any element (Button, icon, avatar) and it becomes the activator.
  • Cross-platform: same API on web and native. On native, the content renders in an RN <Modal> with a tap-outside-to-close backdrop.
  • Keyboard navigation on web: ArrowDown/Up cycle items, Home/End jump to first/last, Enter/Space select, Escape closes.
  • Accessibility: trigger gets aria-haspopup="menu" + aria-expanded; content gets role="menu"; items get role="menuitem".

Preview

Direction:

With icons

import { Button, DropdownMenu } from '@nori-ui/core';
 
export function FileMenu() {
    return (
        <DropdownMenu>
            <DropdownMenu.Trigger>
                <Button variant="secondary">File</Button>
            </DropdownMenu.Trigger>
            <DropdownMenu.Content>
                <DropdownMenu.Item
                    icon={<NewFileIcon />}
                    shortcut="⌘N"
                    onSelect={() => console.log('new')}
                >
                    New File
                </DropdownMenu.Item>
                <DropdownMenu.Item
                    icon={<OpenIcon />}
                    shortcut="⌘O"
                    onSelect={() => console.log('open')}
                >
                    Open
                </DropdownMenu.Item>
            </DropdownMenu.Content>
        </DropdownMenu>
    );
}

With destructive items

Use destructive on items that perform irreversible operations. The item text renders in the danger color.

<DropdownMenu>
    <DropdownMenu.Trigger>
        <Button variant="secondary">Actions</Button>
    </DropdownMenu.Trigger>
    <DropdownMenu.Content>
        <DropdownMenu.Item onSelect={() => console.log('edit')}>Edit</DropdownMenu.Item>
        <DropdownMenu.Separator />
        <DropdownMenu.Item destructive onSelect={() => console.log('delete')}>Delete</DropdownMenu.Item>
    </DropdownMenu.Content>
</DropdownMenu>

With separators and labels

Group related items with DropdownMenu.Separator and annotate groups with DropdownMenu.Label.

<DropdownMenu>
    <DropdownMenu.Trigger>
        <Button>My Account</Button>
    </DropdownMenu.Trigger>
    <DropdownMenu.Content>
        <DropdownMenu.Label>Account</DropdownMenu.Label>
        <DropdownMenu.Item onSelect={() => {}}>Profile</DropdownMenu.Item>
        <DropdownMenu.Item onSelect={() => {}}>Settings</DropdownMenu.Item>
        <DropdownMenu.Separator />
        <DropdownMenu.Item destructive onSelect={() => {}}>Log Out</DropdownMenu.Item>
    </DropdownMenu.Content>
</DropdownMenu>

Anatomy

SubcomponentRole
DropdownMenuRoot — owns open state (controlled or uncontrolled).
DropdownMenu.TriggerElement that toggles the menu. Uses asChild.
DropdownMenu.ContentThe floating menu surface. role="menu".
DropdownMenu.ItemInteractive menu item. role="menuitem".
DropdownMenu.SeparatorVisual and semantic divider. role="separator".
DropdownMenu.LabelNon-interactive section heading. Muted small-caps.

Open state — open, defaultOpen, onOpenChange

// Uncontrolled
<DropdownMenu>
    <DropdownMenu.Trigger><Button>Open</Button></DropdownMenu.Trigger>
    <DropdownMenu.Content>...</DropdownMenu.Content>
</DropdownMenu>
 
// Controlled
const [open, setOpen] = useState(false);
 
<DropdownMenu open={open} onOpenChange={setOpen}>
    <DropdownMenu.Trigger><Button>Open</Button></DropdownMenu.Trigger>
    <DropdownMenu.Content>...</DropdownMenu.Content>
</DropdownMenu>

Accessibility

  • Trigger gets aria-haspopup="menu" and aria-expanded automatically.
  • Content container has role="menu".
  • Each item has role="menuitem" and aria-disabled when disabled.
  • Web keyboard navigation: ArrowDown/Up cycles items; Home/End jumps to bounds; Enter/Space selects; Escape closes.
  • Disabled items are skipped by keyboard nav (negative tabIndex).

Props

PropTypeDefaultDescription
defaultOpenbooleanfalseInitial open state (uncontrolled). @defaultValue false
onOpenChange(open: boolean) => voidFires with the new open state.
openbooleanControlled open state.

Renders the element that opens the menu. Uses asChild — any element passed as children is cloned and becomes the activator. The most common child is a Button. Adds aria-haspopup="menu" and aria-expanded to the child automatically.

The floating menu surface. Accepts children (DropdownMenu.Item, DropdownMenu.Separator, DropdownMenu.Label) and an optional className. Has role="menu". On native it renders inside an RN Modal with a tap-outside-to-close backdrop.

PropTypeDefaultDescription
classNamestring
destructivebooleanfalseRenders the item in a danger/destructive tone.
disabledbooleanfalsePrevents interaction and dims the item visually.
iconReactNodeLeading icon node.
onSelect() => voidFired when the item is selected. Also closes the menu.
shortcutstringKeyboard shortcut hint shown on the trailing edge. Purely presentational — web only, no function key binding.
testIDstring
PropTypeDefaultDescription
classNamestring
testIDstring
PropTypeDefaultDescription
classNamestring
testIDstring

On this page

Preview theme