Tulip Logo IconTulip
Commands

Command Composition

Compose command arrays locally instead of filtering registries by keys or tags.

Tulip no longer recommends registry-style command filtering for new code.

Instead of defining one large registry and then selecting commands with .pick(...), .omit(...), .tags(...), or .toArray(), define atomic command values and compose the array where the menu is rendered.

// app/admin/projects/_config/commands.tsx
export const projectCreateCommand = commandBuilder
  .$type<null>()
  .render(() => <CreateProjectCommand />);

export const projectUpdateStatusCommand = commandBuilder
  .$type<{ id: string } | { id: string }[]>()
  .transform(ensureArray)
  .render(({ data }) => <UpdateProjectStatusCommand projects={data} />);

export const projectArchiveCommand = commandBuilder
  .$type<{ id: string } | { id: string }[]>()
  .transform(ensureArray)
  .render(({ data }) => <ArchiveProjectCommand projects={data} />);

export const projectDeleteCommand = commandBuilder
  .$type<{ id: string } | { id: string }[]>()
  .transform(ensureArray)
  .render(({ data }) => <DeleteProjectCommand projects={data} />);

Usage:

<InlineCommandMenu data={null} commands={[projectCreateCommand]} />

<ResponsiveCommandMenu
  data={project}
  commands={[
    projectUpdateStatusCommand,
    projectArchiveCommand,
    projectDeleteCommand,
  ]}
/>

Why this is preferred

  • the UI clearly shows which commands it renders
  • command definitions stay reusable and typed
  • no tags or selection query API to learn
  • no vague group names like singleCommands, bulkCommands, or globalCommands
  • easier to debug because the final command list is explicit at the render site

When to extract a shared array

Default to local inline composition.

Only extract a shared array when all of these are true:

  • the exact same combination is reused often
  • the group has a clear domain meaning
  • the extracted name is more helpful than the inline array

If that is not true, keep the array inline.

What to avoid

  • registry objects that are later filtered by keys or tags
  • exported array names that only describe mechanics (single, bulk, global, table)
  • large shared command maps that hide what each page actually renders

On this page