Skip to content

Actions

IDropdownMenu ​

A menu opened from a button. Entries are data, and their shape decides what they render as — the same rule ISelect and INavigationMenu follow.

Nothing chosen yet.

Four shapes, one array, decided in this order:

The entryRenders as
The string '-'A separator
Has its own itemsA submenu
Has an onSelectA menu item
Has neitherA group label

Group labels are the intended use:

Danger items ​

danger colours a destructive entry. It does not confirm anything — pair it with useConfirm() when the action cannot be undone.

ts
const items = [
  { label: 'Archive', onSelect: () => archive() },
  '-',
  {
    label: 'Delete invoice',
    danger: true,
    onSelect: async () => {
      if (await confirm({ title: 'Delete this invoice?' }))
        remove()
    },
  },
]

Placement ​

side and align position the menu against its trigger, and sideOffset sets the distance. Like the tooltip, side is a preference — the menu flips when there is not enough room.

Icons ​

An entry's icon takes a Hugeicons icon or any component that renders an SVG.

ts
import { Delete02Icon, PencilEdit02Icon } from '@hugeicons/core-free-icons'

const items = [
  { label: 'Edit', icon: PencilEdit02Icon, onSelect: () => edit() },
  { label: 'Delete', icon: Delete02Icon, danger: true, onSelect: () => remove() },
]

A header ​

The header slot puts a block above the items — an account's name and address, the current workspace, a plan. It is read, not chosen: it takes no stop in the arrow-key order and typeahead ignores it, which a menu entry used as a label cannot say.

An entry with no onSelect renders as a group label, which is the right thing for "Recent files" above a list of them — but wrong for an identity block, which is neither a heading for the items below nor something to act on.

Props ​

PropTypeDefaultDescription
itemsDropdownMenuEntry[][]The menu contents
align'start' | 'center' | 'end''start'Alignment against the trigger
alignOffsetnumber—Offset from the start or end alignment options, in px.
side'top' | 'right' | 'bottom' | 'left''bottom'Preferred side; flips when it will not fit
sideOffsetnumber4Distance from the trigger, in px
unstyledboolean—Drop built-in classes
classstring—Merged with the built-in classes
ui{ content?, header?, item?, label?, separator?, subTrigger?, subContent? }—Per-element class overrides
ts
interface DropdownMenuItemOption {
  label: string
  icon?: IconLike
  disabled?: boolean
  danger?: boolean
  onSelect?: () => void
  /** Makes this entry a submenu. */
  items?: DropdownMenuEntry[]
}

type DropdownMenuEntry = DropdownMenuItemOption | '-'

Slots ​

SlotWhen to use it
triggerThe button that opens the menu. Required
headerA block above the items — an identity, a workspace, a plan
defaultReplaces the items entirely

A dropdown menu is for actions. For choosing a value, use ISelect — it has a model, the right ARIA roles, and typeahead.