Skip to main content
Light Dark System

Page Builder

<zn-page-builder> | ZnPageBuilder
Since 1.0 experimental

A config-driven page composer: a palette of predefined section types, a linear canvas of section cards, and an inspector for editing each section’s content.

The Page Builder composes pages from predefined section types the host application provides. Editors pick sections from the palette, order them on the canvas, and edit each section’s content in the inspector — the output is a plain JSON config (PageState) the host persists and renders however it likes.

Section types are declared as <template type slot="config"> children. The template’s attributes (type, label, icon, icon-library, color, category, description) define the palette entry; its content is stamped into the inspector when a section of that type is selected. Controls are bound by their name attribute: values prefill from the section’s data and write back on change / zn-change.

Undo Save All Changes
<zn-page-builder id="kb-homepage-demo" heading="KB Homepage" subheading="Last updated 7th July 2026" auto-save style="height: 560px">
  <zn-button slot="header-left" icon="refresh-cw@lu" panel-bg
             onclick="this.closest('zn-page-builder').undo()">Undo</zn-button>
  <zn-button slot="header-right" icon="check@lu" color="primary"
             onclick="alert(JSON.stringify(this.closest('zn-page-builder').state, null, 2))">Save All Changes</zn-button>
  <template type="hero" slot="config" label="Hero" icon="star" category="Headers"
            description="Large banner with optional search">
    <zn-input name="title" label="Title"></zn-input>
    <zn-input name="subtitle" label="Subtitle"></zn-input>
  </template>
  <template type="article-list" slot="config" label="Article List" icon="list" category="Content"
            description="A list of KB articles">
    <zn-input name="limit" type="number" label="Max articles"></zn-input>
  </template>
</zn-page-builder>

Listen for zn-page-change to persist the config, or read/write the state property. Types can also be registered programmatically via sectionTypes / registerSectionTypes(), including a renderConfig(section, update) callback for inspector bodies that need real logic.

JavaScript API

  • state — get/set the current PageState. The getter returns a deep copy; the setter replaces the state wholesale (like the config attribute, it does not emit zn-page-change).
  • addSection(type, index?) — insert a new section of a registered type (default: at the end).
  • addSectionToSlot(type, containerId, slotIndex) — insert into a container’s empty slot, honouring its accepts list. Returns the new section, or null if not allowed.
  • undo() / redo() — step through edit history (bounded at 50 entries). There is no built-in toolbar: wire these to your own header buttons or keyboard shortcuts.
  • registerSectionType(type) / registerSectionTypes(types) — programmatic registration, equivalent to slotting templates. Registration is additive — removing an entry from sectionTypes later does not unregister it.
  • restoreAutoSave() — load the auto-saved draft, if one exists within its 1-day TTL (returns false otherwise). See Saving below.

Saving

Wire your own save action into the header slots (rendered only when filled, as in the flow builder) and read state — or persist on every zn-page-change:

<zn-page-builder id="kb-home" heading="KB Homepage" auto-save>
  <zn-button slot="header-right" color="primary" id="save-page">Save All Changes</zn-button>
  <!-- templates… -->
</zn-page-builder>
<script>
  const builder = document.querySelector('#kb-home');
  document.querySelector('#save-page').addEventListener('click', () => {
    fetch('/api/pages/home', {method: 'PUT', body: JSON.stringify(builder.state)});
  });
</script>

Add the auto-save attribute to also snapshot the page into localStorage, keyed by the builder’s id (falling back to its heading), with a 1-day TTL — identical to the flow builder’s auto-save:

<zn-page-builder id="kb-home" auto-save></zn-page-builder>      <!-- every 5 minutes -->
<zn-page-builder id="kb-home" auto-save="2"></zn-page-builder>  <!-- every 2 minutes -->

Without the attribute nothing is saved, and an empty page is never written. While auto-save is on, a status pill in the canvas’s bottom-left flashes “Auto-saved” as each snapshot lands and otherwise shows how long ago the last one happened. When a page is loaded (config / state) that differs from a fresh auto-saved draft, a banner offers to restore the draft — or call restoreAutoSave() yourself.

The config

Every edit emits zn-page-change with the full page state (event.detail.state, also readable via the state property) — plain JSON the host persists and later feeds back in through the config attribute. Sections appear in page order; container sections carry a children array sized to their slot count, with null for empty slots:

{
  "sections": [
    {
      "id": "s-mc41z-0",
      "type": "hero",
      "data": {"title": "Help Centre", "showSearch": true}
    },
    {
      "id": "s-mc42a-1",
      "type": "article-grid",
      "label": "Popular articles",
      "data": {"title": "Popular"},
      "children": [
        {"id": "s-mc42h-2", "type": "article-tile", "data": {"article": "art_42"}},
        {"id": "s-mc42p-3", "type": "article-tile", "data": {"article": "art_7"}},
        null, null, null, null
      ]
    },
    {
      "id": "s-mc43b-4",
      "type": "article-list",
      "data": {"articles": ["art_1", "art_3"]}
    }
  ]
}

Container tiles

A section type with a slots attribute becomes a full-row container: its card renders a 3-column grid of that many child slots beneath it. Drag sections from the palette into empty cells, drag children between cells to reorder, or out onto the page. accepts restricts which types the slots take. Containers can’t be placed inside other containers, and slot contents persist as children on the section (empty slots are null).

<zn-page-builder heading="KB Homepage" style="height: 560px"
  config='{"sections":[{"id":"g1","type":"article-grid","data":{}}]}'>
  <template type="article-grid" slot="config" label="Article Grid" icon="grid_view"
            category="Layout" description="A 3x2 grid of article tiles"
            slots="6" accepts="article-tile">
    <zn-input name="title" label="Grid title"></zn-input>
  </template>
  <template type="article-tile" slot="config" label="Article" icon="article" category="Content"
            description="A single article tile">
    <zn-input name="article" label="Article id"></zn-input>
  </template>
</zn-page-builder>

List sections

Sections that show a set of existing items (categories, articles, …) reference them by id: bind a multi-select by name and the chosen ids persist as an array in the section’s data (e.g. "data": {"articles": ["art_42", "art_7"]}). Options can be inlined as below, or loaded from a backend with <zn-data-select multiple>.

<zn-page-builder heading="KB Category Page" style="height: 420px">
  <template type="article-list" slot="config" label="Article List" icon="list"
            description="Shows a chosen set of KB articles">
    <zn-select multiple name="articles" label="Articles to show">
      <zn-option value="art_1">How to reset your password</zn-option>
      <zn-option value="art_2">Billing FAQ</zn-option>
      <zn-option value="art_3">Getting started guide</zn-option>
    </zn-select>
  </template>
</zn-page-builder>

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@kubex/zinc@1.1.29/dist/components/page-builder/page-builder.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.jsdelivr.net/npm/@kubex/zinc@1.1.29/dist/components/page-builder/page-builder.js';

To import this component using a bundler:

import '@kubex/zinc/dist/components/page-builder/page-builder.js';

Slots

Name Description
config <template type="…"> declarations; never displayed. Each template’s attributes (type, label, icon, icon-library, color, category, description, slots, accepts) declare a palette entry and its content declares the inspector form for that type. slots makes the section a container with that many child slots; accepts is a comma-separated list of the type keys its slots allow.
header-left Actions shown on the left of the header bar.
header-right Actions shown on the right of the header bar.

Learn more about using slots.

Properties

Name Description Reflects Type Default
config The page state as a JSON string. Parsed on set; invalid JSON is ignored with a warning. string ''
sectionTypes Section types to make available, registered into the internal registry. PageSectionType[] []
paletteCollapsed
palette-collapsed
Collapses the left palette. Auto-set when the builder becomes narrow. boolean false
inspectorCollapsed
inspector-collapsed
Collapses the inspector while a section is selected. boolean false
autoSave
auto-save
Auto-save the page to localStorage (1-day TTL). Omit to disable. A bare auto-save saves every 5 minutes; a numeric value sets the interval in minutes (auto-save="2"). Restore with restoreAutoSave(). number | null null
state Replaces the page state wholesale (does not emit zn-page-change). PageState -
restoreAutoSave Load the auto-saved page, if one exists within the 1-day TTL. - -
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
zn-page-change Emitted whenever the page state changes. event.detail.state is the new PageState. -
zn-page-selection-change Emitted when the selected section changes. event.detail.sectionId. -

Learn more about events.

Methods

Name Description Arguments
addSection() Adds a section of a registered type at index (default: end). Returns null for unknown types. type: string, index: number
addSectionToSlot() Adds a new section of a registered type into a container’s slot. Returns null if not allowed. type: string, containerId: string, slotIndex: number

Learn more about methods.

Parts

Name Description
base The grid wrapper.
header The full-width header action bar (only rendered when header slots are filled).
palette The left palette panel.
canvas The centre section-card canvas.
inspector The right panel while a section is selected.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <zn-collapsible>
  • <zn-example>
  • <zn-icon>
  • <zn-input>
  • <zn-page-palette-item>
  • <zn-page-section-card>
  • <zn-tooltip>