Templates
Re-author the sidebar and dashboard structure by composing typed building blocks
Tokens and CSS restyle a fixed layout. The Templates tier changes the layout itself: you compose the sidebar and the dashboard from a closed palette of registered components, expressed as a typed JSON tree — structure as data, never code.
Templates are admin-enabled (templates_enabled on your account — contact Swarmz support). With the capability on, the Branding studio's Templates mode shows the builder; without it, the mode falls back to Guided.
The model
A template names registered component types and wires them to app data through declared bindings:
{
"apiVersion": 1,
"sidebar": {
"apiVersion": 1,
"root": {
"type": "Stack",
"props": { "gap": "md", "padding": "md" },
"children": [
{ "type": "LogoSlot", "bindings": { "logo": "brand.logo" } },
{ "type": "WorkspaceButton", "bindings": { "workspace": "workspace" } },
{ "type": "NavList", "bindings": { "items": "nav.items" } },
{ "type": "Divider" },
{ "type": "CreditsBlock", "bindings": { "credits": "credits" } },
{ "type": "SignOutButton", "props": { "label": "Sign out" } }
]
}
}
}Each surface (sidebar, dashboard) is optional — author one and the other keeps its stock layout. If a template fails validation, the surface falls back to the stock UI; your customers never see a broken shell.
The component palette
Layout primitives — valid on both surfaces:
| Type | Props | Children |
|---|---|---|
Stack | gap, padding, align | yes |
Row | gap, padding, align | yes |
Grid | cols (1/2/3), gap | yes |
Box | gap, padding, align | yes |
Spacer | size | no |
Divider | — | no |
Text | content (≤200 chars), size, weight | no |
Spacing props take none / xs / sm / md / lg; align takes start / center / end / stretch. Text may bind its content to brand.name instead of a literal.
Sidebar leaves — valid in sidebar only:
| Type | Binds | Actions |
|---|---|---|
NavList | nav.items (your resolved navigation) | — |
WorkspaceButton | workspace | openWorkspaceSwitcher |
CreditsBlock | credits | — |
LogoSlot | brand.logo | — |
SignOutButton | — (label prop, ≤40 chars) | signOut |
Dashboard leaves — valid in dashboard only:
| Type | Binds | Actions |
|---|---|---|
Greeting | greeting.text | — |
PromptBlock | prompt | createProject |
ProjectGrid | projects.items | — |
TabStrip | tabs.items (your surface config tabs) | selectTab |
FolderRow | folders.items | openFolder |
NewProjectTile | — (label prop, ≤40 chars) | createProject |
Bindings and actions are closed vocabularies: a component can only bind the sources and wire the actions its registry entry declares. Using a sidebar leaf in a dashboard template (or vice versa) drops that node.
The slot model
Templates build on the app's semantic markup contract: every region a template can address carries a stable data-slot attribute, scoped to its owning data-part. Slot names are public, versioned identifiers — a slot's address is the (part, slot) pair, never a bare slot name.
The published inventory:
| Part | Slots |
|---|---|
SidebarRoot | sidebar (the whole sidebar), logo, nav, workspace (nested inside nav), footer |
DashboardRoot | dashboard, prompt, projects, tabs (nested), folders (nested) |
PopoverContent | workspace-menu (the workspace-switcher popover instance), header, credits, actions, workspaces |
The same hooks are targetable from raw CSS, so a template restructure and a stylesheet can share one addressing scheme.
Validation and caps
A template is treated as untrusted data and normalized before storing or rendering:
apiVersionmust be1(top-level and per surface) — anything else is rejected and the stock UI renders.- Unknown component types, unknown props, out-of-allowlist bindings, and children on childless components are dropped with diagnostics.
- React/DOM escape hatches (
style,ref,key,dangerouslySetInnerHTML, anyon*handler) are dropped unconditionally. - URL props must be
https://or an internal/path; string props are HTML-stripped and length-capped. - Caps: 200 nodes total, 12 levels deep. Exceeding either rejects the template.
The builder surfaces every diagnostic as you edit, so a dropped node is visible long before publish.