Layout and Containers
Overview
Layout components hold other components. They come in two kinds.
Containers are builders: Grid, GridRow, HStack, VStack, Spacer, Tab and Placeholder return a ComponentBuilder, and you nest components inside them with .child(id, component). They are assigned to a static field of a page class like any other component.
Page layouts are not builders: DefaultLayout, FormPageLayout and EmptyLayout return a plain ComponentInfo and are passed as the third argument of PageController to say how the page's chrome is drawn.
CustomComponent sits outside both: it names a Vue component from the frontend registry and gives you a builder for it, which is the escape hatch when the vocabulary on this page is not enough.
import { Grid, GridRow } from "@antelopejs-private/cms/interfaces/cms-base/grid";
import { HStack, Spacer, VStack } from "@antelopejs-private/cms/interfaces/cms-base/stack";
import { Tab } from "@antelopejs-private/cms/interfaces/cms-base/tab";
import { DefaultLayout, EmptyLayout, FormPageLayout } from "@antelopejs-private/cms/interfaces/cms-base/layouts";
import { Placeholder } from "@antelopejs-private/cms/interfaces/cms-base/placeholder";
import { CustomComponent } from "@antelopejs-private/cms/interfaces/cms-base/custom";
Grid, GridRow, HStack, VStack, Spacer, Tab, Placeholder, DefaultLayout, FormPageLayout and EmptyLayout are also exported from the package root. CustomComponent is only exported from cms-base/custom.
Grid
Grid is a container whose direct children are GridRows. The number of columns follows the widest row.
import { Grid, GridRow } from "@antelopejs-private/cms/interfaces/cms-base/grid";
import { KpiCard } from "@antelopejs-private/cms/interfaces/cms-base";
static kpis = Grid({ gap: "1rem" }).child(
"kpiRow",
GridRow()
.child("proposed", KpiCard({ title: "Proposed", fetchUrl: "/metrics/proposed" }))
.child("approved", KpiCard({ title: "Approved", fetchUrl: "/metrics/approved" }))
.child("denied", KpiCard({ title: "Denied", fetchUrl: "/metrics/denied" })),
);
GridOptions has a single field:
| Option | Type | Description |
|---|---|---|
gap | string | Gap between rows and columns, as a CSS length. Defaults to "1rem". |
GridRow() takes no options. Its children are laid out horizontally within the row.
.child() accepts a third argument of extra metadata, which is carried verbatim onto the serialized child alongside its id — that is how a child declares that it spans more than one column:
GridRow().child("chart", chartCard, { colSpan: 2 }).child("list", topListCard);
Stacks
HStack and VStack arrange their children along one axis; Spacer fills the leftover space between them.
import { HStack, Spacer } from "@antelopejs-private/cms/interfaces/cms-base/stack";
static toolbar = HStack({ alignment: "center", distribution: "space-between", spacing: "16px" })
.child("title", titleComponent)
.child("gap", Spacer())
.child("actions", actionsComponent);
StackAlignment is "start" | "center" | "end" | "stretch" and StackDistribution is "start" | "center" | "end" | "space-between" | "space-around" | "space-evenly".
HStackOptions
| Option | Type | Description |
|---|---|---|
alignment | StackAlignment | Vertical alignment of children. Defaults to "center". |
distribution | StackDistribution | Horizontal distribution of children. Defaults to "start". |
spacing | string | Space between children, as a CSS length. Defaults to "8px". |
wrap | boolean | Wrap children onto the next line when the row runs out of space. Defaults to false. |
VStackOptions
| Option | Type | Description |
|---|---|---|
alignment | StackAlignment | Horizontal alignment of children. Defaults to "center". |
distribution | StackDistribution | Vertical distribution of children. Defaults to "start". |
spacing | string | Space between children, as a CSS length. Defaults to "8px". |
SpacerOptions
| Option | Type | Description |
|---|---|---|
minSize | string | Minimum size, as a CSS length. No minimum by default. |
maxSize | string | Maximum size, as a CSS length. No maximum by default. |
grow | number | Flex grow factor. Defaults to 1. |
The three builders apply their defaults into the options they carry, so a Spacer() with no arguments still ships { grow: 1 }.
Tabs
Tab declares a tab bar. Each TabItem names a slot, and the component that belongs in a tab is attached as a child with that slot.
import { Tab, TabVariant } from "@antelopejs-private/cms/interfaces/cms-base/tab";
import { Color, Size } from "@antelopejs-private/cms/interfaces/cms-base/types";
static tabs = Tab({
items: [
{ label: "Overview", slot: "overview", icon: "i-ph-gauge" },
{ label: "Members", slot: "members", icon: "i-ph-users", badge: 12 },
],
variant: TabVariant.pill,
color: Color.primary,
size: Size.medium,
persistState: true,
stateKey: "tenant-tabs",
})
.child("overview", overviewComponent, { slot: "overview" })
.child("members", membersTable, { slot: "members" });
TabProps
TabProps extends BaseComponentProps.
| Option | Type | Description |
|---|---|---|
items | TabItem[] | The tabs. Required. |
color | Color | Colour of the active tab. |
size | Size | Control size. |
variant | TabVariant | TabVariant.pill or TabVariant.link. |
orientation | AxeOrientation | Direction of the tab bar. |
unmountOnHide | boolean | Discard a tab's content when it is not active. |
persistState | boolean | Remember the active tab. |
stateKey | string | Key the state is stored under. |
TabItem
| Field | Type | Description |
|---|---|---|
label | string | Tab label. Required. |
slot | string | Slot name the tab's content is attached to. Required. |
icon | string | Leading icon. |
badge | string | number | BadgeProps | Badge on the tab. |
disabled | boolean | Make the tab unselectable. |
shortcut | string | Keyboard shortcut. |
avatar | { src?: string; alt?: string; size?: Size } | Leading avatar. |
BadgeProps is { label?: string | number; color?: string; variant?: "solid" | "outline" | "soft" | "subtle"; size?: Size }. Note that its variant union is its own, narrower than ButtonVariant.
TabEvents.TAB_CHANGE is "CmsComponent.Tab.Change".
Page Layouts
A page layout is the third argument of PageController. It is optional: a page that omits it is registered with DefaultLayout() and its default options.
import {
PageController,
pagesCategory,
RegisterPage,
} from "@antelopejs-private/cms/interfaces/cms/page";
import { DefaultLayout } from "@antelopejs-private/cms/interfaces/cms-base/layouts";
@RegisterPage()
export class OverviewPage extends PageController(
"overview",
{ displayName: "Overview", icon: "i-ph-gauge", category: pagesCategory },
DefaultLayout({ hideHeader: true }),
) {
static content = overviewGrid;
}
DefaultLayout(options?) returns the ComponentInfo for cms-default-layout.
| Option | Type | Description |
|---|---|---|
fullWidth | boolean | Let the content span the whole panel instead of a constrained column. Defaults to true. |
hideHeader | boolean | Omit the page header. |
Dashboard pages are full-width unless they opt out, which is what a table, a grid of cards or a chart wants. Set fullWidth: false for a page that only holds a form, where a constrained column stays readable — or use FormPageLayout, which is exactly that:
import { FormPageLayout } from "@antelopejs-private/cms/interfaces/cms-base/layouts";
FormPageLayout(options?: Omit<DefaultLayoutOptions, "fullWidth">): ComponentInfo
The generated new and edit pages of a table view use it, so a form page you write by hand matches them without your having to remember the flag.
EmptyLayout() takes no options and returns the ComponentInfo for cms-empty-layout, with an empty options object. Use it for pages that draw their own chrome — sign-in and onboarding screens, for instance.
Because they return a plain ComponentInfo rather than a ComponentBuilder, the page layouts have no .child(), .meta() or .watch().
Placeholder
Placeholder renders a labelled box. It exists so a layout can be assembled and reviewed before the real components are written. It takes PlaceholderOptions:
import { Placeholder } from "@antelopejs-private/cms/interfaces/cms-base/placeholder";
static preview = Placeholder({ label: "Chart goes here", height: "200px" });
| Option | Type | Description |
|---|---|---|
label | string | Text shown in the box. |
height | string | CSS height. Defaults to "120px". |
width | string | CSS width. Auto by default. |
CustomComponent
CustomComponent(componentName) returns a bare ComponentBuilder for a frontend component resolved by name. Nothing else in cms-base is involved: the options you pass with .options() are handed to that component as-is, and the component itself comes from a Nuxt layer registered with AddNuxtLayer.
import { CustomComponent } from "@antelopejs-private/cms/interfaces/cms-base/custom";
static activity = CustomComponent("CmsAiActivityView").meta({
name: "Activity",
icon: "i-ph-clock-counter-clockwise",
});
Since the return value is an ordinary builder, .options(), .child(), .meta(), .watch(), .onCreated() and .onFilter() all apply, and the component can hold cms-base components as children.
CustomComponent is also how a table view's formContainer.pages.*.customPage option is used: you register your own page carrying a custom component at the slug the table view would otherwise generate — see Tables.
Next Steps
- Tree - Hierarchical nodes with selection and lazy loading.