Skip to content

[Feature] Block Kit: Add layout options to ActionsBlock and sticky support for blocks #364

Description

@pagelab

Feature Request

Problem

When building plugin admin pages with Block Kit, there is no way to control the layout of ActionsBlock elements or make blocks stick to the viewport during scrolling. This limits the UX of plugin pages that need toolbar-like patterns (e.g., tab bars with action buttons).

Use case

A plugin admin page uses an ActionsBlock as a navigation bar with several tab-like buttons and a destructive "Reset" button. Two problems arise:

  1. No justify-between layout on ActionsBlock: All buttons are flush-left. There is no way to push the last button to the far-right edge, which would create a clear visual separation between navigation actions and destructive actions.

  2. No sticky positioning: When a page has many form fields, the user must scroll back to the top to access the navigation. A sticky bar would keep it always visible.

Current behaviour

ActionsBlockComponent renders a hardcoded class:

function ActionsBlockComponent({ block, onAction }) {
  return (
    <div className="flex flex-wrap gap-2">
      {block.elements.map((el, i) => (
        <div key={el.action_id ?? i}>{renderElement(el, onAction)}</div>
      ))}
    </div>
  );
}

BlockRenderer wraps each block in a plain <div> with no positioning options:

function BlockRenderer({ blocks, onAction }) {
  return (
    <div className="flex flex-col gap-4">
      {blocks.map((block, i) => (
        <div key={block.block_id ?? i}>{renderBlock(block, onAction)}</div>
      ))}
    </div>
  );
}

Proposed API additions

1. layout property on ActionsBlock

interface ActionsBlock extends BlockBase {
  type: "actions";
  elements: Element[];
  layout?: "start" | "space-between" | "end" | "center";
}

Rendering mapping:

Value CSS class
"start" (default) justify-start
"space-between" justify-between
"end" justify-end
"center" justify-center

2. sticky property on BlockBase

interface BlockBase {
  block_id?: string;
  sticky?: boolean;
}

When sticky: true, the BlockRenderer wrapper <div> would add sticky top-0 z-10 bg-kumo-app (or similar), pinning the block to the top of the scrollable area.

Example usage

return {
  type: "actions",
  layout: "space-between",  // <-- new
  sticky: true,              // <-- new
  elements: [
    { type: "button", action_id: "tab_1", label: "Section 1", style: "primary" },
    { type: "button", action_id: "tab_2", label: "Section 2", style: "secondary" },
    { type: "button", action_id: "reset", label: "Reset", style: "danger" },
  ],
};

Alternatives considered

  • CSS injection via page:fragments: Only affects the front-end site, not the admin UI.
  • Multiple ActionsBlocks in a ColumnsBlock: Columns render as equal-width (grid-cols-2 / grid-cols-3), so this does not produce a narrow/wide toolbar layout.
  • Using separate admin pages per section: Works but loses the tab-switching UX and requires full page reloads.

Impact

These two small additions would unlock toolbar and navigation patterns for all plugins without breaking any existing Block Kit usage (both properties are optional with backward-compatible defaults).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions