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:
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:
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.
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:
{{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 |:
{{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 empty 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:
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 for the code-authored path, and the Component cookbook for copy-paste code recipes.
Last updated
Was this helpful?
