> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# PageBlocks

> Why gavAI pages are JSON trees of typed blocks instead of compiled code, what the 10 built-in blocks do, and how the page document is shaped.

export const PullQuote = ({children}) => <p className="gv-pull-quote">
    <span className="gv-pull-quote__mark" aria-hidden="true">“</span>
    <span className="gv-pull-quote__body">{children}</span>
  </p>;

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const Explanation = ({children}) => <div className="gv-code-explainer__explanation">{children}</div>;

export const Code = ({children}) => <div className="gv-code-explainer__code">{children}</div>;

export const CodeExplainer = ({eyebrow, title, children}) => <div className="gv-code-explainer">
    {(eyebrow || title) && <header className="gv-code-explainer__header">
        {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
        {title && <h3 className="gv-code-explainer__title">{title}</h3>}
      </header>}
    <div className="gv-code-explainer__grid">{children}</div>
  </div>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="CONCEPT" />

<PageMeta audience="Developers" readingTime="8 min" level="Concepts" />

A gavAI page is a JSON document, not compiled code. The document is a tree of typed **blocks** — NavShell at the root, then Hero, Form, Table, and the rest nested inside — and the runtime turns that tree into a rendered page on every request. By the end of this page you will know what a block is, why pages are stored as documents, which 10 blocks ship in the platform, and how the document is shaped.

## A page, in 15 lines

Here is a page document that renders a navigation shell with a hero banner, a feature grid, and a footer:

```json page.json theme={null}
{
  "version": "v0.4",
  "metadata": { "slug": "landing", "title": "Acme Co." },
  "schema": { },
  "tree": {
    "block": "NavShell",
    "props": {},
    "children": [
      { "block": "Hero",         "props": { "headline": "Order management for plumbers." } },
      { "block": "FeaturesGrid", "props": { "columns": 3 } },
      { "block": "Footer",       "props": {} }
    ]
  }
}
```

That is the whole page. There is no JSX, no React component to compile, no deploy artifact — just a JSON value that the runtime knows how to render. You can `GET` this document, edit it locally, and `PATCH` it back.

<PullQuote>The value is the page.</PullQuote>

## Why store pages as documents

Storing the page as a document — rather than as code — is the load-bearing decision in the platform. Four properties follow from it.

**Portable.** A page is a value, so it can move around. You can export a workspace's pages as JSON, copy them into another workspace, and have a working app — assuming the destination workspace has providers wired for the same capabilities (more on that in [Capabilities](/concepts/capabilities)).

**Versionable.** Every change to a page document creates a new version. Rolling back is a single API call. Diffing two versions is a JSON diff, which the runtime and the Builder both understand.

**AI-readable.** A page is a tree of typed nodes that a language model can read and patch reliably. The Builder MCP exposes `get_block_schema` so an AI agent can introspect any block type at runtime, compute a patch, and apply it — the same operations the Builder UI performs.

**Multi-renderer.** The same document drives server-side rendering on first paint, WebAssembly hydration for interactivity, and static HTML export. You write one document; the platform handles the rendering target.

## The mental model: blocks as a typed tree

Think of a page as a **typed file system** — except instead of folders and files, the nodes are blocks. Each block has a `block` type (the name), a `props` object (its configuration), and an optional `children` array (more blocks nested inside).

The analogy breaks down because folders don't render themselves and files don't have schemas. Drop the metaphor once the shape clicks.

```mermaid theme={null}
flowchart TD
    R[NavShell<br/>root]
    R --> H[Hero<br/>props.headline, props.cta]
    R --> F[FeaturesGrid<br/>props.columns, props.items]
    R --> T[Form<br/>props.schema_table, props.on_submit]
    R --> Z[Footer<br/>props.links]
```

Two rules govern the tree:

* **Type-checked at write time.** Every block has a Zod-validated props schema. The runtime rejects a page document that contains a block whose props don't match — a `Hero` missing a `headline` never reaches a rendered page.
* **Bindings are by name, not by reference.** A `Form` block binds to a schema table by name (`"schema_table": "leads"`); a `Table` block reads records from a table by name. The binding is resolved at runtime against the workspace's schema and data.

## The 10 block types

Every workspace ships with these. Each one has a typed props schema; the linked reference page is the source of truth for what `props` it accepts.

<ArrowList>
  <ArrowItem>**NavShell** — Top-level container that provides sidebar or topbar navigation chrome and slots all other blocks inside the main content area.</ArrowItem>
  <ArrowItem>**Hero** — Large banner section with a headline, subheading, and optional CTA button.</ArrowItem>
  <ArrowItem>**FeaturesGrid** — Multi-column grid of feature cards, each with an icon, title, and body text.</ArrowItem>
  <ArrowItem>**Cta** — A focused call-to-action band with a single headline and button.</ArrowItem>
  <ArrowItem>**Footer** — Bottom-of-page section with grouped link columns and a copyright line.</ArrowItem>
  <ArrowItem>**SectionDivider** — Visual separator that pulls spacing values from the workspace theme tokens.</ArrowItem>
  <ArrowItem>**Form** — Inline form bound to a schema-defined table; fires a capability (such as `sendEmail`) on submit.</ArrowItem>
  <ArrowItem>**Table** — Paginated, filterable, sortable view of records from a schema table.</ArrowItem>
  <ArrowItem>**DetailPanel** — Single-record view with read mode and edit mode, toggled by user permissions.</ArrowItem>
  <ArrowItem>**KanbanBoard** — Drag-drop workflow board that groups records by a status field.</ArrowItem>
</ArrowList>

## The page document, in detail

Every page document has four top-level keys. The example at the top of this page showed them in use; here is what each one carries.

<CodeExplainer>
  <Code>
    ```json page.json theme={null}
    {
      "version": "v0.4",
      "metadata": { "slug": "landing", "title": "Acme Co." },
      "schema": { },
      "tree": {
        "block": "NavShell",
        "props": {},
        "children": [
          { "block": "Hero",         "props": {  } },
          { "block": "FeaturesGrid", "props": {  } },
          { "block": "Footer",       "props": {  } }
        ]
      }
    }
    ```
  </Code>

  <Explanation>
    **`version`** identifies the document schema revision. The runtime uses this to apply any necessary migrations before rendering — so an old page document keeps working when the platform's document schema evolves.

    **`metadata`** holds the page's slug (used as the URL path) and display title. Anything that describes the page itself rather than its contents lives here.

    **`schema`** declares the data tables this page reads and writes. Capability calls and block data-bindings are validated against this declaration at runtime. See [Schema](/concepts/schema) for the shape.

    **`tree`** is the block tree. A root block (almost always a `NavShell`) names a block type, carries its `props`, and nests further blocks under `children`. Every node in the tree is one block instance.
  </Explanation>
</CodeExplainer>

## How a page renders

When a request lands at `acme.gavai.app/landing`, the runtime walks the tree top-down:

```mermaid theme={null}
sequenceDiagram
    participant U as Visitor
    participant W as Tenant Worker
    participant D as Page document
    participant S as Schema / data
    U->>W: GET /landing
    W->>D: load page document
    W->>W: validate block props against schemas
    W->>S: resolve data bindings (Form, Table, DetailPanel)
    W-->>U: rendered HTML + WASM hydration bundle
```

The first paint is server-rendered HTML. The runtime then ships a WebAssembly bundle that hydrates the page for interactivity — `Form` submissions, `Table` filters, `KanbanBoard` drag-drop. The page document is the input to both stages; you do not write two versions.

## When to add a block vs. compose existing ones

Most apps fit inside the 10 built-in blocks. A landing page is `NavShell` → `Hero` → `FeaturesGrid` → `Cta` → `Footer`. A customer portal is `NavShell` → `Table` → `DetailPanel`. A signup flow is `NavShell` → `Hero` → `Form`.

The 10 cover the common surfaces. If your page wants a shape none of them produce — an embedded video player, a custom chart — that is a sign you need a custom block, which is a separate (and rarer) extension path. Most page changes are *prop changes on existing blocks*, not new block types.

## Related

<Columns cols={2}>
  <BrandCard eyebrow="ACTIONS" title="Capabilities" href="/concepts/capabilities" icon="zap">
    What the buttons and forms in your blocks actually do at runtime.
  </BrandCard>

  <BrandCard eyebrow="DATA" title="Schema" href="/concepts/schema" icon="database">
    The data declaration that Form, Table, and DetailPanel bind to.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="Block reference" href="/pageblocks/overview" icon="cube">
    Every block type, with full props, validation rules, and examples.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="The page document" href="/pageblocks/page-document" icon="file-code">
    The full shape of the JSON document, version by version.
  </BrandCard>
</Columns>
