Askr
UI & Components

Menu, Dropdown, and Context Menu

Use menu, dropdown, and context menu with the current published Askr packages.

  • @askrjs/ui/menu0.0.13
  • @askrjs/ui/dropdown0.0.13
  • @askrjs/themes/dropdown-menu0.0.13
  • @askrjs/themes/context-menu0.0.13

How to use menu, dropdown, and context menu

Menu, Dropdown, and Context Menu should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep menu, dropdown, and context menu labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Dropdown, DropdownContent, DropdownItem, DropdownTrigger } from '@askrjs/themes/components';

<Dropdown>
  <DropdownTrigger aria-label="Project actions">Actions</DropdownTrigger>
  <DropdownContent>
    <DropdownItem onSelect={archive}>Archive</DropdownItem>
    <DropdownItem onSelect={duplicate}>Duplicate</DropdownItem>
  </DropdownContent>
</Dropdown>

Purpose

This page covers two distinct headless primitives that get themed into three components. `Menu` (`@askrjs/ui/menu`) is a plain, always-rendered list of actions with roving keyboard navigation — think the list inside a Menubar. `Dropdown` (`@askrjs/ui/dropdown`) adds the open/close, trigger, and positioned-portal behavior on top, and is what the themed `DropdownMenu` and `ContextMenu` are both built from.

Install and import

Import `Menu`, `MenuItem`, `MenuGroup`, and friends from `@askrjs/ui/menu`; import `Dropdown`, `DropdownTrigger`, `DropdownContent`, and `DropdownItem` from `@askrjs/ui/dropdown`. For styled output, `@askrjs/themes/dropdown-menu` and `@askrjs/themes/context-menu` both resolve to the same themed `Dropdown` implementation under different export names — there's no separate `ContextMenu` component in the themes package, just a relabeled `Dropdown`.

Live examples

A dropdown menu example is a `Dropdown` with a `DropdownTrigger` button and a `DropdownContent` holding one or more `DropdownItem`s, optionally grouped with `DropdownGroup` and `DropdownLabel`. Because `ContextMenu` is the same component, the only real difference in a working example is what you wire to `onOpenChange` — a click for a dropdown, a `contextmenu` event handler you write yourself for a right-click menu.

Anatomy

`Dropdown` is the open-state root; `DropdownTrigger` opens it and `DropdownPortal` carries `DropdownContent` outside the normal DOM flow. Inside, `DropdownItem` is the selectable row, `DropdownGroup` clusters related items, `DropdownLabel` gives a group a heading, and `DropdownSeparator` divides sections. `Menu` mirrors this with `MenuItem`, `MenuGroup`, and `MenuSeparator`, but without a trigger or portal — it's meant to be embedded directly, not popped open.

State model

`Dropdown` takes `open`, `defaultOpen`, and `onOpenChange`, same shape as Tooltip and Dialog — control it yourself if the open state needs to react to something outside the trigger, like a right-click position. `Menu` has no open state at all; its only state-shaped props are `orientation` and `loop`, which govern how arrow-key navigation wraps between items rather than whether the menu is visible.

Keyboard and accessibility

Both primitives use `role="menu"` on their content and `role="menuitem"` on each row, matching the WAI-ARIA menu pattern; `Dropdown`'s trigger additionally carries `aria-expanded`, `aria-controls`, and `aria-haspopup` so assistive tech knows it opens a menu. `Menu` exposes `aria-orientation` for its roving-tabindex arrow navigation, and disabled items on either primitive get `data-disabled` so they're skipped in that navigation rather than just visually dimmed.

Styling and tokens

`DropdownContent` carries `data-state`, `data-side`, and `data-align` for open/close transitions and directional positioning, the same pattern as Tooltip and Popover content. `DropdownItem` supports a `variant` of `'default'` or `'destructive'`, which the default theme uses to color delete-style actions distinctly — since `ContextMenu` shares this CSS wholesale, styling one styles the other.

API

`DropdownContent` accepts `forceMount`, `side`, `align`, and `sideOffset`; `DropdownTrigger` additionally takes `variant` (`'default' | 'ghost'`) and `size` (`'md' | 'icon'`) to match common trigger button shapes. `DropdownItem`'s `onSelect` fires on activation and its `variant` prop is `'default' | 'destructive'`; `MenuItem` has the same `onSelect` shape but no variant, since Menu doesn't own theming concerns.

Edge cases

Because `ContextMenu` is literally `Dropdown` under an alias, it has no built-in right-click trigger — you supply the `contextmenu` event listener, call `preventDefault`, and drive `open`/`onOpenChange` yourself, typically positioning the content at the pointer coordinates rather than relative to a trigger element. Watch out for browser context menus firing alongside yours if you forget the `preventDefault` call, and for `DropdownContent` clipping if it isn't mounted through `DropdownPortal`.

See Menubar for a themed, always-visible menu bar built on the same `Menu` primitive. See Popover for anchored content that isn't structured as a role="menu" list, and Tooltip for hover-only hints rather than actionable items.