[data-reveal]{opacity:1!important;transform:none!important}
Components

Layout & containers

Structural components — Grid, stacks, tabs, and placeholders — that arrange other components on a page.

Layout components fetch and render no data of their own. They arrange other components — the forms, tables, trees, charts & widgets you attach to a page. Every declared position still carries its own permission: the page's container field, each nested row or stack, and each leaf append their id to the path. A role needs every level it should render; granting the container alone does not expose its children.

Each is a plain component builder, so it composes like every builder does (see Pages & components). You nest with .child():

signature
.child(id: string, component: Component, metadata?: { slot?: string; [key: string]: unknown }): this

id is the child's key under that parent (must be unique — a duplicate logs a warning). component is any other builder, so nesting is arbitrary. .child() returns the parent, so calls chain into a serializable tree the shared frontend renders. metadata carries per-child data: Tab uses { slot }, Grid rows accept { colSpan }; otherwise it is ignored.

Grid is the one container with a shape rule: a Grid's direct children must be GridRows, with the leaf components inside the rows. Stacks and tabs take any component as a direct child.

Grid

What it renders — a CSS grid. Each GridRow becomes a row; its children spread across equal-width columns. The column count is derived from the densest row, so a two-item and a three-item row in the same grid align to a three-column track. gap spaces both rows and columns; the column track stays fixed at every viewport width — the grid does not collapse on narrow screens.

Import

src/pages/dashboard.ts
import { Grid, GridRow } from "@antelopejs-private/cms/interfaces/cms-base/grid";

Options

OptionTypeDefaultPurpose
gapstring"1rem"CSS gap between rows and columns.

GridRow() takes no options — it is a pure row marker.

Features

  • Column count auto-computed from the widest row; rows may differ in length.
  • A row child can span columns with child metadata: .child("wide", …, { colSpan: 2 }).
  • Any builder may sit inside a row, including stacks, tabs, or a nested grid.

HStack / VStack / Spacer

What it renders — a flexbox container. HStack lays children left-to-right, VStack top-to-bottom. alignment controls the cross-axis (perpendicular line-up), distribution the main-axis (how free space is shared), and spacing the gap between children. HStack can additionally wrap overflowing children. A Spacer between children expands to absorb leftover space — e.g. logo · Spacer · actions pins the logo left and the actions right.

Import

src/pages/layout.ts
import { HStack, VStack, Spacer } from "@antelopejs-private/cms/interfaces/cms-base/stack";

OptionsHStackOptions and VStackOptions share alignment, distribution, and spacing; only HStack adds wrap.

OptionTypeDefaultPurpose
alignmentStackAlignment"center"Cross-axis alignment of children.
distributionStackDistribution"start"Main-axis distribution of children.
spacingstring"8px"CSS gap between children.
wrapbooleanfalseHStack only — wrap children to the next line.

StackAlignment is "start" | "center" | "end" | "stretch". StackDistribution is "start" | "center" | "end" | "space-between" | "space-around" | "space-evenly".

Spacer is a flexible gap that works in either stack direction:

OptionTypeDefaultPurpose
grownumber1Flex-grow weight — how greedily it expands.
minSizestringundefinedMinimum size (CSS unit).
maxSizestringundefinedMaximum size (CSS unit).

Features

  • Nest freely: stacks inside stacks build full scaffolds (header / sidebar / content / footer).
  • Spacer respects its minSize / maxSize bounds.
  • alignment: "stretch" makes children fill the cross axis — handy for equal-width cards in a row.

Tab

What it renders — a tab strip plus a panel area. Each TabItem is a clickable tab with a label, slot, and optional icon, badge, avatar, keyboard shortcut, and disabled state. The active tab shows the children whose .child(…, { slot }) matches its slot.

Import

src/pages/tabs.ts
import { Tab, TabVariant } from "@antelopejs-private/cms/interfaces/cms-base/tab";

Options (TabProps)

OptionTypeDefaultPurpose
itemsTabItem[]requiredThe tabs (see fields above).
colorColorprimaryAccent color (primary, success, …).
sizeSizemdTab sizing (tinyhuge, i.e. xsxl).
variantTabVariantpillpill or link styling.
orientationAxeOrientationhorizontalhorizontal or vertical strip.
unmountOnHidebooleantrueDestroy an inactive panel's children rather than keep them alive.
persistStatebooleanfalseRemember the selected tab across reloads.
stateKeystring"tab"Storage key for the persisted selection.

A child binds to a tab by slot: .child("overview", Tree({ … }), { slot: "overview" }); the item's slot and the child's slot must match. Color, Size, and AxeOrientation come from cms-base/types.

Features

  • Per-tab icons, badges, avatars, and keyboard shortcuts.
  • unmountOnHide selects keep-alive vs. rebuild-on-show for hidden panels.
  • persistState + stateKey keep the user on the same tab across navigation.
  • Tabs emit a change event (TabEvents.TAB_CHANGE) other components can watch (Actions & reactivity).

Placeholder

What it renders — a dashed, patterned box of the given height (and optional width) with its label centered. A development stand-in for blocking out a layout before the real components exist; it fetches nothing.

Import

src/pages/scaffold.ts
import { Placeholder } from "@antelopejs-private/cms/interfaces/cms-base/placeholder";

Options (PlaceholderOptions)

OptionTypeDefaultPurpose
labelstringundefinedText shown inside the box.
heightstring"120px"Box height (CSS unit).
widthstringundefinedBox width (CSS unit, auto if unset).

Features

  • Zero data dependencies — safe to drop anywhere to visualize spacing.
  • Pairs naturally with stacks and grids while scaffolding a page.
Swap each Placeholder for its real builder as the page comes together.

Composing a layout

Containers nest to build a whole screen. Here a Grid row splits the page into a sidebar tree and a tabbed detail panel:

src/pages/workspace.ts
import { Grid, GridRow } from "@antelopejs-private/cms/interfaces/cms-base/grid";
import { Tab } from "@antelopejs-private/cms/interfaces/cms-base/tab";
import { Tree, TreeSelectionBehavior } from "@antelopejs-private/cms/interfaces/cms-base/tree";

static layout = Grid({ gap: "1rem" }).child(
  "row1",
  GridRow()
    .child(
      "files",
      Tree({
        title: "Files",
        fetchUrl: "/api/files",
        selectionBehavior: TreeSelectionBehavior.replace,
      }),
    )
    .child(
      "detail",
      Tab({
        items: [
          { label: "Overview", icon: "i-ph-info", slot: "overview" },
          { label: "Settings", icon: "i-ph-gear", slot: "settings" },
        ],
      })
        .child(
          "overview",
          Tree({ fetchUrl: "/api/overview", selectionBehavior: TreeSelectionBehavior.replace }),
          { slot: "overview" },
        )
        .child(
          "settings",
          Tree({ fetchUrl: "/api/settings", selectionBehavior: TreeSelectionBehavior.replace }),
          { slot: "settings" },
        ),
    ),
);

For dashboards, the same nesting feeds widgets — a Grid of GridRows holding KpiCards and ChartCards. See Charts & widgets.