May 24 2026 · Base UI · React · Tailwind v4

One menu, many triggers

Base UI lets a single menu be opened by triggers scattered anywhere in the tree, each handing it a payload. One mounted menu for a whole list of rows, instead of one per row.

  • Q3 roadmapEdited 2 days ago
  • Brand guidelinesEdited 1 week ago
  • Launch checklistEdited 3 weeks ago
Every row's ⋯ button opens the same menu instance. It knows which row it came from because the trigger hands it a payload — pick an action and the toast names the right one.

The shape of the problem

Picture a list of rows, each with a ⋯ button that opens a small actions menu: rename, duplicate, delete. The obvious way to build it is to put a menu inside every row. Ten rows, ten menus, each with its own open state, its own positioning logic, its own portal. It works, it's wasteful, and it gets worse the longer the list grows.

What you actually want is one menu. The actions are identical for every row — the only thing that changes is which row you opened it from. So why mount the thing ten times?

How Base UI does it

This is what Base UI calls a detached trigger, and the wiring is small. You create a handle, give it to a single Menu.Root, and give the same handle to as many Menu.Triggers as you like:

The trigger and the menu don't have to sit anywhere near each other in the markup — that's the detached part. They're connected by the handle, not by being nested.

The payload is the clever bit

A shared menu would be useless if it couldn't tell which row opened it. That's what payload is for. Each trigger passes its own row's data, and Menu.Root hands it back through a render function — {({ payload }) => …} — so the menu can label itself and wire its actions to the right item. In the demo, the action you pick fires a toast naming the row it acted on, which is the whole proof: one menu, but it always knows where it was opened from.

It positions itself correctly with no work from me, too. The menu anchors to whichever trigger opened it, so even though there's a single Menu.Positioner, it appears under the right ⋯ every time.

What it's stripped of

Not much, honestly — this is close to how I'd ship it. A real version would wire the actions to actual mutations and almost certainly add a confirmation step before the destructive one. The structure underneath, though, is exactly this: one Menu.Root, one handle, a trigger per row.

Why it's more than a tidiness win

Mounting one menu instead of ten isn't only lighter. It's also why the behaviour stays consistent. There's a single place where the actions, the keyboard handling, and the styling live, so every row's menu is identical by construction rather than by discipline. When one component owns every instance of an interaction, that interaction can't quietly drift apart across the list. It's work you do once and then stop having to think about.