GitHubnpm

Accent theme

Base radius

GitHubnpm

Accent theme

Base radius

Colour scheme

Docs menu

Guides


Primitives
Forms
Layout
Feedback
Overlays
Navigation
Data Display
AI

Dropdown Menu

role="menu" on a popover="auto" element. Outside-click and Escape dismissal come free from the popover. Arrow-key roving between items is the one part of the WAI-ARIA menu pattern that has to be wired up by hand, there's no native menu element yet. A morph variant further down grows the panel out of its own trigger instead of fading in place.

Usage
import { Button, DropdownMenu, MenuChevron, MenuItem, MenuSeparator } from "@kernelui-lib/react";

function ActionsMenu() {
  return (
    <DropdownMenu
      align="end"
      offset={4}
      className="my-menu"
      render={<Button variant="secondary" iconEnd={<MenuChevron />}>Actions</Button>}
    >
      <MenuItem onSelect={() => {}}>Edit</MenuItem>
      <MenuItem render={<a href="/docs" />}>Documentation</MenuItem>
      <MenuSeparator />
      <MenuItem destructive onSelect={() => {}}>
        Delete
      </MenuItem>
    </DropdownMenu>
  );
}

Playground

Toggle every adjustable prop and watch the component — and the code — update live.

Unstyled

Same component — Kernel look stripped. See Platforms.

placement
align
delete item destructive
duplicate item disabled
Usage
<DropdownMenu render={<Button variant="secondary" iconEnd={<MenuChevron />}>Actions</Button>}>
  <MenuItem onSelect={() => {}}>Edit</MenuItem>
  <MenuItem onSelect={() => {}}>Duplicate</MenuItem>
  <MenuSeparator />
  <MenuItem destructive onSelect={() => {}}>
    Delete
  </MenuItem>
</DropdownMenu>

Props

Props for Dropdown Menu.
ComponentPropType
DropdownMenupresentation"popover" (default) | "disclosure"
DropdownMenurenderthe trigger (required)
DropdownMenurenderContentRenderProp — replace the popup
DropdownMenuclassNamestring | (state) => string
DropdownMenuplacement"top" | "bottom" | "left" | "right"
DropdownMenualign"start" | "center" | "end"
DropdownMenuoffsetnumber (px)
MenuItemrenderRenderProp — e.g. <a> / router Link
MenuItemclassNamestring | (state) => string
MenuItemonSelect() => void
MenuItemdestructiveboolean
MenuChevronno props — drop into a trigger's iconEnd; rotates via aria-expanded
DropdownMenuMorphrenderthe trigger (required) — same shape as DropdownMenu's
DropdownMenuMorphrenderContentRenderProp — replace the popup
DropdownMenuMorphclassNamestring | (state) => string
DropdownMenuMorphplacement"top" | "bottom" | "left" | "right"
DropdownMenuMorphalign"start" | "center" | "end"
DropdownMenuMorphoffsetnumber (px)

Native disclosure presentation

Set presentation="disclosure" for a menu anchored within its parent, such as navigation in a fixed mobile header. This renders a real details with a summary trigger. HTML handles toggling; CSS handles repeated entry and exit through::details-content. It creates no top-layer backdrop and does not change page scrolling. Escape, outside clicks, Tab leaving the menu, and item selection close it; arrow keys move between items.

Render a summary, not a button inside a summary. The disclosure stays in its ancestor's stacking context and can be clipped by ancestor overflow. It does not flip to avoid viewport edges; use the default popover presentation when those positioning features are needed. Browsers without ::details-content transitions still toggle natively, with immediate closing. Reduced motion is immediate.

Usage
<DropdownMenu
  presentation="disclosure"
  align="end"
  render={<Button render={<summary />}>Menu</Button>}
>
  <MenuItem render={<a href="/work" />}>Work</MenuItem>
  <MenuItem render={<a href="/about" />}>About</MenuItem>
</DropdownMenu>
Usage
<kernel-dropdown-menu presentation="disclosure" align="end">
  <summary slot="trigger">Menu</summary>
  <kernel-menu-item href="/work">Work</kernel-menu-item>
  <kernel-menu-item href="/about">About</kernel-menu-item>
</kernel-dropdown-menu>

Morph variant

DropdownMenuMorph is the same menu — it sharesMenuItem/MenuSeparator directly — with a different opening move: the panel's own box grows out of the trigger's exact footprint (width, height, and corner radius all animate) instead of scaling and fading in place.

That one visual difference forces a mechanical one: a popover's hide can't be deferred, so there's no way to let the shrink-back- to-the-trigger animation finish before the panel actually disappears while staying popover="auto". This variant is popover="manual" instead, with its own outside- click and Escape dismissal wired up by hand — the same tradeoffContextMenu and Combobox already make for their own manual popovers. Selecting an item still plays the full shrink animation before the menu closes.

The trigger's own icon rotation/cross-fade (the + that becomes a × below) is authored in the demo, not baked into the component — same convention as MenuChevron: what renders is always visible at the call site.

Usage
import { DropdownMenuMorph, MenuItem, MenuSeparator } from "@kernelui-lib/react";

<DropdownMenuMorph render={<PlusTrigger />}>
  <MenuItem onSelect={() => {}}>New file</MenuItem>
  <MenuItem onSelect={() => {}}>New folder</MenuItem>
  <MenuSeparator />
  <MenuItem onSelect={() => {}}>Upload</MenuItem>
</DropdownMenuMorph>

Accessibility

Opening moves focus to the first item. Arrow Up/Down, Home, and End move between items. Selecting an item, or pressing Escape, closes the menu. Link items keep native navigation, modifier-clicks, and new-tab behaviour — selection closes the menu withoutpreventDefault.