Layout & containers
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():
.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'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
import { Grid, GridRow } from "@antelopejs-private/cms/interfaces/cms-base/grid";
Options
| Option | Type | Default | Purpose |
|---|---|---|---|
gap | string | "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
import { HStack, VStack, Spacer } from "@antelopejs-private/cms/interfaces/cms-base/stack";
Options — HStackOptions and VStackOptions share alignment, distribution, and spacing; only HStack adds wrap.
| Option | Type | Default | Purpose |
|---|---|---|---|
alignment | StackAlignment | "center" | Cross-axis alignment of children. |
distribution | StackDistribution | "start" | Main-axis distribution of children. |
spacing | string | "8px" | CSS gap between children. |
wrap | boolean | false | HStack 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:
| Option | Type | Default | Purpose |
|---|---|---|---|
grow | number | 1 | Flex-grow weight — how greedily it expands. |
minSize | string | undefined | Minimum size (CSS unit). |
maxSize | string | undefined | Maximum size (CSS unit). |
Features
- Nest freely: stacks inside stacks build full scaffolds (header / sidebar / content / footer).
Spacerrespects itsminSize/maxSizebounds.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
import { Tab, TabVariant } from "@antelopejs-private/cms/interfaces/cms-base/tab";
Options (TabProps)
| Option | Type | Default | Purpose |
|---|---|---|---|
items | TabItem[] | required | The tabs (see fields above). |
color | Color | primary | Accent color (primary, success, …). |
size | Size | md | Tab sizing (tiny…huge, i.e. xs…xl). |
variant | TabVariant | pill | pill or link styling. |
orientation | AxeOrientation | horizontal | horizontal or vertical strip. |
unmountOnHide | boolean | true | Destroy an inactive panel's children rather than keep them alive. |
persistState | boolean | false | Remember the selected tab across reloads. |
stateKey | string | "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.
unmountOnHideselects keep-alive vs. rebuild-on-show for hidden panels.persistState+stateKeykeep 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
import { Placeholder } from "@antelopejs-private/cms/interfaces/cms-base/placeholder";
Options (PlaceholderOptions)
| Option | Type | Default | Purpose |
|---|---|---|---|
label | string | undefined | Text shown inside the box. |
height | string | "120px" | Box height (CSS unit). |
width | string | undefined | Box 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.
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:
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.