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

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:

OptionTypeDescription
gapstringGap 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

OptionTypeDescription
alignmentStackAlignmentVertical alignment of children. Defaults to "center".
distributionStackDistributionHorizontal distribution of children. Defaults to "start".
spacingstringSpace between children, as a CSS length. Defaults to "8px".
wrapbooleanWrap children onto the next line when the row runs out of space. Defaults to false.

VStackOptions

OptionTypeDescription
alignmentStackAlignmentHorizontal alignment of children. Defaults to "center".
distributionStackDistributionVertical distribution of children. Defaults to "start".
spacingstringSpace between children, as a CSS length. Defaults to "8px".

SpacerOptions

OptionTypeDescription
minSizestringMinimum size, as a CSS length. No minimum by default.
maxSizestringMaximum size, as a CSS length. No maximum by default.
grownumberFlex 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.

OptionTypeDescription
itemsTabItem[]The tabs. Required.
colorColorColour of the active tab.
sizeSizeControl size.
variantTabVariantTabVariant.pill or TabVariant.link.
orientationAxeOrientationDirection of the tab bar.
unmountOnHidebooleanDiscard a tab's content when it is not active.
persistStatebooleanRemember the active tab.
stateKeystringKey the state is stored under.

TabItem

FieldTypeDescription
labelstringTab label. Required.
slotstringSlot name the tab's content is attached to. Required.
iconstringLeading icon.
badgestring | number | BadgePropsBadge on the tab.
disabledbooleanMake the tab unselectable.
shortcutstringKeyboard 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.

OptionTypeDescription
fullWidthbooleanLet the content span the whole panel instead of a constrained column. Defaults to true.
hideHeaderbooleanOmit 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" });
OptionTypeDescription
labelstringText shown in the box.
heightstringCSS height. Defaults to "120px".
widthstringCSS 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.