BridgeHandbookDraft
For agents
Menu
ExperimentalmoleculeActions

ActionMenu

The one ⋮ overflow menu for a row or card's actions.

import { … } from "@brdgapp/ui/components/action-menu"

Usage

ActionMenu is Bridge's one ⋮ menu: the actions for a row, a card or a profile. The trigger, the floating panel and the rows all come from here, so every menu opens, moves and closes the same way. The usual mistake is building a second kebab from scratch (a raw dropdown, a hand-made portal, a different dots icon).

When to use

  • Secondary actions on a row or card: Make intro, Edit, Stop sharing.
  • The caret of a split button (pass renderTrigger).
  • Rows with a second line (description) when each choice needs a word of explanation.

When not to use

  • For the main action. Show it as a button. Use Button instead.
  • For picking one filter value. Use FilterPill instead.
  • For two related actions that fit side by side. Use ButtonGroup instead.

One of each

Bridge has one button, one label, one menu, one tooltip, one confirm dialog and one table. Before building a control, look for it here. Never hand-make a second version (a <button> with pill classes, a <span> chip, a new kebab), even if it looks the same today: copies drift.

Anatomy

Make intro
Edit contact
Stop sharing
  1. Trigger. A vertical-dots IconButton (or your own, via renderTrigger).

  2. Panel. 248px wide, raised surface, 12px corners, the menu shadow. Opens below, aligned to the trigger's right edge.

  3. Row. Icon + label, 13px. Optional muted second line. danger rows are red.

  4. Separator. A thin line between groups.

Examples

On a contact row

Open it with the keyboard: Tab to ⋮, then Enter and the arrow keys.

Leah Okonkwo

Partner, Saltmarsh Partners

A row that stays open

Shows its own result, then the menu can be closed.

As a split button's caret

renderTrigger hangs the menu off a ButtonGroupItem.

States

RestHoverFocusDisabled
Trigger
Row
Edit contact
Edit contact
Edit contact
Edit contact
Danger row
Stop sharing
Stop sharing
Stop sharing
Stop sharing
StateWhat happens
HighlightedThe row under the pointer or keyboard gets a light fill.
Disabled60% opacity; skipped by the keyboard.
Danger--danger-text, with a red tint when highlighted.

Behavior

  • Built on Base UI Menu: focus moves into the menu, arrow keys move between rows, typing jumps to a row, Esc closes and focus returns to the trigger.
  • It follows its trigger when the page scrolls (Bridge OS closed it instead).
  • Choosing a row closes the menu, unless closeOnClick={false} (for a row that shows its own result, like "Copied!").
  • By default the trigger's click doesn't reach a clickable row behind it.
  • href makes a link row; external opens it in a new tab. For your router's link, pass render={<Link href=… />}.

Do and don't

Do

Name the trigger after what it acts on: "Actions for Leah Okonkwo".

Don't

Leave every kebab on a list called "More options".

Do

Put danger rows last, after a separator.

Don't

Mix "Stop sharing" between harmless actions.

Content

  • Row labels start with a verb: Make intro, Copy profile link, Stop sharing.

Accessibility

  • Rows are real menu items, announced with their position ("2 of 4").
KeysAction
EnterSpace↓Open the menu from the trigger.
↑↓Move between rows.
EnterChoose a row.
EscClose and return to the trigger.

API reference

ActionMenu

The menu and its trigger.

PropTypeDefaultDescription
ariaLabelstring"More options"Names the default trigger.
size"sm" | "md""md"Trigger size.
widthnumber248Panel width in px.
iconReactNode—Trigger icon (default: vertical dots).
onOpenChange(open) => void—Know when it opens, e.g. to load data.
renderTrigger(props, { open }) => ReactElement—A custom trigger. Spread props onto it so keyboard opening works too.
stopPropagationbooleantrueKeep the trigger's click off a clickable row.
childrenReactNode | (close) => ReactNode—The rows.

ActionMenuItem

One row.

PropTypeDefaultDescription
iconReactNode—A bare Lucide icon.
descriptionReactNode—A muted second line.
onClick() => void—What it does.
hrefstring—Makes it a link.
externalbooleanfalseOpen the link in a new tab.
renderReactElement—Your router's link element.
dangerbooleanfalseRed.
disabledbooleanfalseCan't be chosen.
closeOnClickbooleantrueClose the menu after choosing.

ActionMenuSeparator

A line between groups.

PropTypeDefaultDescription

useActionMenu

Returns { close }, to close the menu from a custom control inside it.

PropTypeDefaultDescription

Lineage

What it's built from and where it's used, read from the code. When you change it, check these too.

Built from
IconButton
Used by components
None
Used in templates
FeedRecord page

Known gaps

Where the code still differs from the system. Follow the system, not the gap.

  • Bridge OS's internal links used Next.js Link automatically. The package doesn't depend on Next.js, so pass render={<Link href=… />}; href alone makes a plain link (a full page load).
  • Bridge OS's ContactActionsMenu builds some rows as plain buttons with ACTION_MENU_ITEM_CLASS. Those aren't menu items, so arrow keys skip them. Move them to ActionMenuItem with closeOnClick={false}.
  • Bridge OS's user menu, app launcher and app switcher still use a raw Radix dropdown. They move to this component.

Sources

  • Bridge OS components.md → ActionMenu / ActionMenuItem; src/components/ui/action-menu.tsx.
  • Base UI 1.9 Menu docs (bundled in the package).