Tulip Logo IconTulip
Commands

Menus

Command menu components, behavior, and when to use each.

Menus are the presentation layer for command arrays. They all receive data, optional meta, and commands.

Shared contract

All menus follow the same pattern:

<SomeCommandMenu
  data={rowOrContextData}
  meta={optionalMeta}
  commands={taskCommands.pick({ updateStatus: true, delete: true }).toArray()}
/>
  • data: validated by each command's .input(...)
  • meta: optional additional context for meta-aware builders
  • commands: array output from .pick(...).toArray() or .omit(...).toArray()

Menus also accept command lifecycle props:

  • onSuccess: called after a command mutation succeeds
  • onError: called after a command mutation fails
  • onSettled: called after success or error
  • fallback: rendered when an individual command is filtered out by schema, conditions, or permission

DropdownCommandMenu closes itself after onSuccess runs.

InlineCommandMenu

Best for visible actions in headers/toolbars and desktop detail views.

Behavior:

  • renders actions as inline controls/buttons
  • good for primary, high-frequency actions
<InlineCommandMenu data={null} commands={projectCommands.pick({ create: true }).toArray()} />

Loading state:

<InlineCommandMenuLoading />

Best when horizontal space is limited or actions are secondary.

Behavior:

  • renders a compact "more" trigger
  • opens a dropdown list of commands
<DropdownCommandMenu
  data={task}
  commands={taskCommands.pick({ updateStatus: true, updateAssignee: true, delete: true }).toArray()}
/>

Loading state:

<DropdownCommandMenuLoading />

ContextCommandMenu + ContextCommandMenuContent

Best for right-click interactions on rows/cards.

Behavior:

  • opens on context trigger (right click / long press depending platform)
  • supports empty state label when the commands array is empty
<ContextCommandMenu>
  <ContextCommandMenuTrigger>...</ContextCommandMenuTrigger>
  <ContextCommandMenuContent
    data={row}
    commands={customerCommands.pick({ delete: true }).toArray()}
    emptyLabel="Geen acties beschikbaar"
  />
</ContextCommandMenu>

FloatingCommandMenu

Best for bulk selection action bars in tables.

Behavior:

  • fixed floating action bar
  • explicit state controls visibility (open/closed)
  • usually used with selected rows array as data
  • supports empty state label when the commands array is empty
<FloatingCommandMenu
  state={selected.length > 0 ? "open" : "closed"}
  data={selected}
  commands={taskCommands.pick({ updateStatus: true, delete: true }).toArray()}
/>

ResponsiveCommandMenu

Best default for detail topbars when you want desktop/mobile behavior out of the box.

Behavior:

  • desktop: inline command menu
  • mobile/tablet: dropdown command menu
<ResponsiveCommandMenu
  data={project}
  commands={projectCommands.pick({ updateStatus: true, archive: true, restore: true, delete: true }).toArray()}
/>

Loading state:

<ResponsiveCommandMenuLoading />

Empty states and filtered commands

Menu empty states only check the command array passed into the menu. They do not know whether every individual command later failed schema validation, conditions, or permission checks.

If a command should leave a visible placeholder when it is filtered out, pass a plain React node as fallback. The fallback is not rendered inside command context, so do not use command primitives there.

<ContextCommandMenuContent
  data={row}
  fallback={<span className="px-2 py-1.5 text-muted-foreground text-sm">Geen acties beschikbaar</span>}
  commands={customerCommands.pick({ delete: true }).toArray()}
/>

Choosing the right menu

  • use InlineCommandMenu for immediate visibility
  • use DropdownCommandMenu for compact layouts
  • use ContextCommandMenu for row/card context actions
  • use FloatingCommandMenu for bulk actions
  • use ResponsiveCommandMenu for adaptive topbar actions

On this page