> For the complete documentation index, see [llms.txt](https://docs.peakcommerce.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.peakcommerce.com/developers/native-components.md).

# Native Components

A **native component** is built from the same blocks the page editor uses — headings, text, containers, buttons, images — arranged into a layout that PeakCommerce renders directly.

You do not write any code for one. You describe what you want to the component agent, you arrange the blocks yourself, or you promote something you already built on a page — and the result appears in the palette on every surface you scope it to.

## Native or code?

Both kinds of component live side by side in the Component Library. Pick by what you need:

|                  | Native component                                                                                     | Code component                                          |
| ---------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| How you build it | Describe it to the agent, or arrange blocks                                                          | Write HTML and CSS                                      |
| Styling          | Inherits your tenant's look automatically                                                            | Whatever you write                                      |
| Editing later    | Every element is individually selectable in the page editor                                          | The whole component is one opaque block                 |
| Live data        | Yes — data bindings and lists                                                                        | Yes — data bindings; lists need hand-written JavaScript |
| Best for         | Anything showing information: account summaries, invoice and subscription lists, banners, CTAs, FAQs | Bespoke visual designs the blocks cannot express        |

**Start native.** Reach for a code component only when the blocks genuinely cannot produce the design you need.

## The blocks

Every native component is a tree of these ten blocks — the complete vocabulary:

| Block             | What it is                                                                                          |
| ----------------- | --------------------------------------------------------------------------------------------------- |
| **Heading**       | A title — h1, h2, or h3, with alignment and color                                                   |
| **Text**          | Body copy, in three sizes                                                                           |
| **Container**     | The layout workhorse — stacks or rows its children, with padding, background, border, and alignment |
| **Columns**       | Side-by-side columns that can stack on tablet or mobile                                             |
| **Button**        | A call to action with a link, sized and colored                                                     |
| **Image**         | A picture, optionally linked                                                                        |
| **Divider**       | A horizontal separator, with or without a visible line                                              |
| **Spacer**        | Fixed vertical breathing room                                                                       |
| **FAQ Accordion** | Expandable question-and-answer rows                                                                 |
| **Repeat**        | Renders its children once per row of a data binding — how lists work (below)                        |

If a design cannot be expressed in these blocks, it is a job for a [code component](/developers/custom-components.md).

## Static and dynamic are the same thing

There is no mode to choose. A component that shows fixed text is a layout of blocks with the text typed in. A component that shows live data is the *same layout* with `{{…}}` placeholders in it, plus the data bindings those placeholders read.

So "make this dynamic" is never a rebuild. You add a binding and swap a word for a placeholder.

## Placeholders

Three kinds, and they are the same ones code components use:

| Placeholder          | Means                                                                   |
| -------------------- | ----------------------------------------------------------------------- |
| `{{fieldName}}`      | A value whoever places the component can edit — a title, a button label |
| `{{data.name.path}}` | A value from a data binding — `{{data.account.name}}`                   |
| `{{item.path}}`      | The current row inside a **Repeat** — `{{item.invoiceNumber}}`          |

A placeholder naming something the component does not declare stays on screen as literal text, so a typo is visible immediately rather than silently blank.

## Formatting amounts and dates

Raw values are not presentable. An amount straight out of a binding renders as `1234.5`, and a date renders as a machine timestamp. Add a **formatter** after a `|`:

| Written                                     | Renders                |
| ------------------------------------------- | ---------------------- |
| `{{item.amount \| currency:USD}}`           | `$1,234.50`            |
| `{{item.amount \| currency:item.currency}}` | The row's own currency |
| `{{item.dueDate \| date}}`                  | `Sep 1, 2026`          |
| `{{item.updatedAt \| datetime}}`            | `Sep 1, 2026 3:04 PM`  |
| `{{data.usage.total \| number}}`            | `1,234,567`            |
| `{{data.plan.discount \| percent}}`         | `12.5%`                |

**Always format money and always format dates.**

For money, prefer `currency:item.currency` over a hardcoded code — it uses whatever currency that row is actually billed in, so the component still reads correctly for customers billed in euros.

Dates render in **the viewer's timezone**, not the server's. This matters more than it sounds: an unformatted date is in UTC, so a due date can show the wrong day for anyone outside it.

A misspelled formatter is rejected when you save, rather than quietly falling back to the raw number.

## Showing a list — the Repeat block

Most useful dynamic components are lists: invoices, subscriptions, payments. A **Repeat** block renders its contents once for each row.

Point `source` at a data binding — `{{data.openInvoices}}` — and inside it use `{{item.…}}` for each row's values. `{{item.…}}` means nothing outside a Repeat and will be rejected there.

**Always fill in the Repeat's&#x20;*****empty*****&#x20;text.** When a customer has nothing to show, a component that renders as blank space reads as a broken page rather than as "you have no open invoices". The empty text also appears if the data cannot be loaded, so a customer never sees an empty box with no explanation.

A Repeat cannot contain another Repeat.

## Save as component — promote what you built on a page

You do not have to start in the Component Library. Build a layout directly on a page — or have the agent build one — then select the **top block** of the part you want to keep. A **Save as component** panel appears beneath the inspector, and it promotes that block and everything inside it into a reusable component.

Along the way it offers the literal strings it found — headings, labels, button text — as **editable fields**: anything you tick becomes a field someone can change when placing the component, without editing the component itself. Three kinds of value are deliberately not offered: values that already carry a `{{…}}` token (they are already dynamic — freezing one into a field default would bake the token in as literal text), empty text, and colors (promoting every hex value would bury the two or three fields you actually want under a dozen you do not).

Not every selection can convert, and the panel is explicit rather than lossy: if the selection contains a sandboxed code component, a checkout or pricing system component, or a raw-HTML block, the save is refused and the panel names **exactly which block** is the problem and why. Refusing beats the alternative — a component that silently dropped a band when you saved it.

What does convert is guaranteed to save: the extraction produces only trees that pass the save-time gate, so a selection that renders in front of you never turns into a validation error.

This is also the fast loop for working with the agent: **ask for something close, adjust it by hand on the page, keep the result.** Without it, getting a small change into an agent-built component means re-prompting until the agent happens to land on it.

A component saved this way gets broad placement defaults so it is immediately findable in the Component Library; open it there to narrow where it appears and who sees it.

## Where it appears

A component can save successfully and still render **nowhere** — placement, audience, and the Active switch all gate it. You no longer have to discover this by searching palettes: the component editor shows a **banner** that tells you, before you go looking, exactly whether and where the component will appear — and when it will not, why, with the fix:

* **Inactive** — an inactive component never renders. Turn on **Active** when you are ready for it to appear.
* **No editor surfaces selected** — it will not appear in any palette. Pick at least one editor surface under **Placement**. This is the one true blocker: as configured, the component cannot appear anywhere.
* **Admin-only audience** — only admins will see it; no customer, partner, CSR, or sales role is in its audience. Add the roles that should see it under **Access**, if it is meant for end users.

One nuance the banner also handles: a **surface/audience mismatch** — placed in a customer-facing palette, say, while its audience includes no customer-facing role — is flagged as a **notice, not a blocker**. That is deliberate, because the two settings are different axes: **editor surfaces are about authoring** (whose palette the block appears in — admins and CSRs build pages there), while **audience roles gate runtime visibility** (who sees it rendered). An admin-only component placed in a customer palette is a legitimate thing to build, so the banner asks you to confirm it rather than stopping you.

If you built the component with the agent, it sets placement and audience from your conversation — the banner is still the quickest way to confirm they landed where you expected.

## What the agent will not build for you

Checkout, payment capture, plan and pricing selection, signup and billing forms, and the CSR and partner account widgets are **existing components** with real billing logic behind them.

Ask for one of those and the agent will point you at the real component and offer to place it, rather than assembling something that looks like a payment form out of blocks. A hand-built one would look convincing and take payments incorrectly.

## If a layout is rejected

Layouts are checked when you save. If one cannot render, the save is refused and every problem is listed with the exact block it is in.

This is deliberate: it is better to be told immediately than to have a component saved, published, and quietly broken on a customer's page. When you are working with the agent it usually never reaches you — the agent gets the same list and fixes its own layout before showing you anything. And layouts promoted via **Save as component** pass by construction.

## Examples included with your tenant

Four working native components ship with a new tenant. Open them in the Component Library to see how they are put together, or duplicate one as a starting point:

| Component            | Shows                                                                           |
| -------------------- | ------------------------------------------------------------------------------- |
| **Welcome Banner**   | Greets the signed-in visitor by name                                            |
| **Account Summary**  | Account number, status, and balance                                             |
| **Open Invoices**    | Unpaid invoices with amount and due date, one row each, with a Pay link per row |
| **My Subscriptions** | Subscriptions and their status                                                  |

## Current limits

Worth knowing before you plan a component around them:

* **No conditional blocks.** A block cannot be shown only when a value has a particular status. Where possible, filter at the binding instead — Open Invoices asks the server for unpaid invoices only, so every row it renders genuinely needs a Pay button.
* **Borders are all-or-nothing per block.** A line on just one edge is not available yet; use spacing to separate rows.

See also: [Custom components](/developers/custom-components.md) for the code-authored path, and the [Component cookbook](/developers/component-cookbook.md) for copy-paste code recipes.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.peakcommerce.com/developers/native-components.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
