Skip to content

Page Contracts

A PageContract is a JSON object that fully describes a page. The backend builds it; the frontend renders it.

Contract Anatomy

Every contract follows the same hierarchical structure:

PageContract
 +-- version: "1"
 +-- shell: "product" | "immersive"
 +-- page
 |    +-- key, title, breadcrumbs, actions, filters
 +-- layout
      +-- template: "stack" | "sidebar" | "dashboard" | "wizard"
      +-- regions
           +-- content: [ BlockDescriptor, ... ]
           +-- main:    [ BlockDescriptor, ... ]
           +-- aside:   [ BlockDescriptor, ... ]
           +-- header:  [ BlockDescriptor, ... ]

BlockDescriptor
 +-- type: "dense_table" | "form_panel" | ...
 +-- key:  unique string
 +-- data: { ... block-specific payload }
 +-- meta: { fullBleed?: boolean, lazyProp?: string, ... }

Immutable at render time

The contract is read-only once it reaches the frontend. All mutations happen on the backend, which sends a new contract via Inertia partial reload.

Shells

The shell wraps the entire page and provides chrome (navigation, header, footer):

ShellDescriptionChrome
productFull application shell with sidebar navigationOffcanvas sidebar + top header + breadcrumbs + optional tabs
immersiveDistraction-free shell for focused tasksMinimal top bar with close button

The product shell provides zero padding on its content area. Layouts and blocks control all spacing (see Layout Templates).

When the Inertia shared prop admin_tabs is present, the product shell renders a tab bar between the page header and content area.

Layouts

The layout defines how regions are arranged inside the shell content area:

TemplateDescriptionRegions
stackSingle column, blocks stacked verticallycontent, header?, footer?
sidebarTwo-column layout (main + aside panel)main, aside, header?
dashboardGrid with metrics row and body contentmetrics, header?, content, aside?
wizardMulti-step form with progress indicatorcontent (+ meta.steps, meta.actions)

See Layout Templates for detailed region documentation and the padding model.

Blocks Overview

Blocks are the building blocks of every page. Each block type has a specific data shape. See the Block Catalog for full details.

Block TypeCategoryDescription
dense_tableData DisplaySortable, filterable data table with row actions
detail_panelData DisplayKey-value detail view with sections
card_gridData DisplayGrid of cards with status, actions, and metadata
link_listData DisplaySimple list of labeled links with optional icons
status_stripData DisplayHorizontal strip of status/metric badges
activity_timelineData DisplayChronological list of events
form_panelFormsDynamic form with field groups and validation
form_builderFormsDrag-and-drop form field designer
sentence_builderFormsNatural language rule composer
condition_treeFormsNested AND/OR condition tree editor
empty_stateContentPlaceholder for empty or first-use states
markdown_panelContentRendered markdown content block
tabbed_panelContentTabbed container for nested blocks
action_gridContentGrid of action cards (quick actions / shortcuts)
workflow_progressData DisplayLinear workflow state indicator with timestamps
metric_cardVisualizationSingle KPI with trend indicator
chart_panelVisualizationMulti-series chart (bar, line, area, pie)
flow_editorVisualizationVisual workflow/node graph editor
kanban_boardInteractiveDrag-and-drop kanban board with columns

Rendering Flow

The journey from backend to rendered page:

1. Backend (PHP)        Build PageContract array/object
                           |
2. Inertia              Serialize to JSON, send as page props
                           |
3. ContractPage         Receive contract prop
                           |
4. Shell Registry       Resolve shell component (product/immersive)
                           |
5. Layout Registry      Resolve layout component (stack/sidebar/...)
                           |
6. Block Registry       For each BlockDescriptor in each region,
                        resolve the block component by type
                           |
7. React Render         Shell > Layout > Blocks rendered as tree

Custom registrations

The registries are populated by registerDefaults(). You can also register custom shells, layouts, or blocks for extension-specific UIs.

TypeScript Types

ts
interface PageContract {
    version: '1';
    shell: 'product' | 'immersive' | (string & {});
    page: PageMeta;
    layout: LayoutDescriptor;
    resources?: PageResources;
}

interface PageMeta {
    key: string;
    title: string;
    subtitle?: string;
    icon?: string;
    badge?: PageBadge;
    favoritable?: boolean;
    breadcrumbs?: Breadcrumb[];
    actions?: PageAction[];
    filterTabs?: PageFilterTab[];
    activeFilterTab?: string;
}

interface LayoutDescriptor {
    template: 'stack' | 'sidebar' | 'dashboard' | 'wizard' | (string & {});
    regions: Record<string, BlockDescriptor[]>;
    meta?: Record<string, unknown>;
}

interface BlockDescriptor<TData = Record<string, unknown>> {
    type: string;       // block type key (e.g. 'dense_table')
    key: string;        // unique key within the page
    data: TData;        // typed data payload
    variant?: string;
    title?: string;
    subtitle?: string;
    actions?: PageAction[];
    meta?: Record<string, unknown>;
    // Reserved meta keys:
    //   fullBleed: boolean  -- skip horizontal padding (edge-to-edge)
    //   lazyProp: string    -- Inertia prop key for lazy-loaded data
    //   loading: boolean    -- show loading skeleton
}

See API Reference for complete type definitions.

Action Hierarchy

Page and block actions use a unified type hierarchy (SOLID principles):

TypePurpose
ActionBaseBase: id, label, icon?, intent?, disabled?
ExecutableActionPOST/PUT/DELETE with optional confirmation (bulk, detail, grid)
ConditionalActionRow-level with visible_when/disabled_when/loading_when
PageActionPage-level actions (extends ActionBase)
ts
// Page-level actions (header buttons)
actions?: PageAction[];

// Row-level actions (DataTable rows)
rowActions?: ConditionalAction[];

// Bulk actions (DataTable multi-select)
bulkActions?: ExecutableAction[];

ExecutableAction extends ActionBase with href, method, and confirmation?: ActionConfirmation. ConditionalAction extends ExecutableAction with visible_when, disabled_when, and loading_when callback props.

MIDDAG © 2015-2026