# Alus Builder — AI Generation Reference (non-MCP / copy-paste) You are generating a page for **Alus Builder** (the Alusio visual website builder). The user will **copy your JSON and Import it** into the builder. Follow this exactly. ## OUTPUT CONTRACT — read first Output **one JSON code block** — the minimal page schema: ```json { "elements": [ /* AlusElement[] — the page tree */ ] } ``` - `elements` is the page tree (array of elements). This shape alone is enough — the Import dialog accepts it directly. - You do **NOT** need any envelope. The `{ "source": "alusioCopiedElements", "version", "exportedAt", … }` wrapper from the builder's Export is **optional** metadata, never required — do not hand-author it. - A single bare element node `{ "id", "type", "props", … }` and a bare array `[ {…} ]` are also accepted, but prefer `{ "elements": [...] }`. - Do **NOT** build a global header/footer — the site adds them automatically around every page. - Every page MUST be **mobile-responsive**: add `tablet`/`mobile` overrides — stack multi-column grids to 1 column and reduce tall section padding on phones. Standardize spacing/radius/shadow/color/font-size through `var(--theme-*)` tokens, never hardcoded values. See the Mobile & responsive + theme-token sections below. - Output ONLY the JSON block (no commentary) unless the user asks you to explain. > There are no tools to call here — **everything you need is in this document**: the rules, the full > element type list, the template-type gating, and worked examples. --- ## You are building a `single_product` template Halaman detail satu produk dengan gambar, nama, harga, deskripsi, varian, quantity, add-to-cart, dan opsional countdown/share/wholesale. This document is **scoped to that one template** — the element list, reference, rules, and examples below only cover what belongs on it. Elements this template forbids are omitted. For the unscoped, all-templates reference, drop the `?template=` query param. ## Template type — `single_product` You are generating this template type. Dynamic elements (productTitle, postContent, …) only resolve inside their compatible template (or a query loop on that source). ### `single_product` — Halaman detail satu produk dengan gambar, nama, harga, deskripsi, varian, quantity, add-to-cart, dan opsional countdown/share/wholesale. - **Required:** productTitle, productPrice - **Recommended:** productImageGallery, productImage, productDescription, productVariants, productQuantity, productCustomerNote, addToCartButton, productStock, productSku, productSalePercent, productSalePrice, productRegularPrice, productCountdown, productShare, productWholesaleTable, button, miniCheckout, section, container - **Forbidden:** checkoutItems, checkoutShipping, checkoutPayment, checkoutMessages, postTitle, postContent, postFeaturedImage, postExcerpt, postMeta, pageTitle, pageContent, pageFeaturedImage, courseTitle, courseDescription, courseImage, coursePrice, courseCurriculum, courseEnroll, cartItems, cartSummary, nameField, emailField, phoneField, textareaField, selectField, checkboxField, customHtml, submitButton - Root: `
` · Typical structure: section > container (style.layout.display=grid, gridTemplateColumns='1fr 1fr', gap=40px) > [block (gallery): productImageGallery, block (info): productTitle/Price/Description/Button(linkType='checkout')] - Notes: Layout standar 2-kolom: gambar kiri, info+CTA kanan. ProductImageGallery (multi image) lebih disarankan daripada ProductImage (single) untuk single_product. Button dengan linkType='checkout' menambah ke cart + redirect /checkout. MiniCheckout = inline 1-product checkout (alternatif). Jangan campur checkoutForm/Payment/etc di sini — itu untuk template checkout. # Alus Builder — AI Generation Guide (Entry Point) > **Read this first.** This is the thin orientation layer for generating valid `BuilderSchema` > JSON. It does NOT repeat the full block spec — it points you at the right resource, lists the > mistakes that actually break output, and gives you a decision tree. The detail lives in the > resources indexed at the bottom; pull them on demand instead of guessing. --- ## 0. The 30-second contract 1. Output shape is always `{ "elements": AlusElement[] }`. Nothing else at the root. 2. Every element needs a unique `id` (short random string, e.g. `"el-a1b2"`) and a `type`. 3. Only `section` / `container` / `block` carry arbitrary `children`. `iconList` carries `iconListItem` children only; `accordion` carries `accordionItem` children only. Nothing else may have `children`. 4. `props` is required (can be `{}`). `style` and `selectorConfig` are optional — omit when unused. 5. **The save tool is the validator.** `pages_set_builder_schema` / `kits_set_builder_schema` / `themes_set_template` validate server-side AND run the contrast lint in the same call, returning a fix-list on error. Submit the schema **as an object** directly — do NOT run a separate `validate_schema` pass first (it just doubles the payload). If the save returns errors, fix and resubmit. Use `validate_schema` for a schema you are NOT yet saving — e.g. one you will paste into the builder's Import Schema box; it runs the identical gate (accepts the object directly — no need to stringify). 6. Colors/fonts/radius come from the site **Theme** via CSS vars. Use `var(--theme-*, #fallback)` — never invent token names, always include a hex fallback. --- ## 1. Decision tree — what element do I reach for? **Need a layout box?** - Full-bleed band (one per logical page section) → `section` (`htmlTag: "section"`). - Centered content wrapper inside a section → `container`. **It is ALREADY boxed + centered** at the theme's container width (`--theme-container-width`, ~1100px) via CSS — do NOT set `maxWidth: "1100px"` or `marginLeft/Right: "auto"` for the default. Only set `maxWidth` to make it **narrower** (e.g. `"720px"` for a reading column) or wider; margins stay auto automatically. - A flex/grid child cell (column, card, row item) → `block`. - Columns side-by-side → `container`/`block` with `style.layout.display: "grid"` + `gridTemplateColumns: "1fr 1fr"`. **Never** reach for legacy `twoColLayout`/`tabs`/`col1` — removed. **Need text?** - Big title → `heading` (`level` h1–h6). Inline emphasis only → `mode: "rich"` + `richText`. - Paragraph with inline formatting / multiple blocks → `richText` (`body` = HTML). - Plain paragraph, no toolbar needed → `basicText`. **Need it to pull live content (product/post/page/course)?** - → Use a **Dynamic element** (`productTitle`, `postContent`, …) OR a primitive + entity tag (`heading` + `{product_title}`). See §3. These only resolve inside the matching **template type** or a `queryLoop` on that data source — elsewhere they render placeholders. **Need a repeating grid of items (recent products, blog list)?** - → ONE template `block`/card with a **root-level `queryLoop`** (sibling of `props`, NOT inside it), inside a grid `container`. The block IS the repeated template (repeat-self model). Descendants read the current row via entity tags. Don't hand-emit N copies. See §4. **Need a carousel?** → `slider` with each child a slide (or one `queryLoop` child for dynamic). **Need a checklist / feature list?** → `iconList` > `iconListItem[]` (NOT generic blocks + icons). **Need an FAQ?** → `accordion` > `accordionItem[]`. When unsure which element exists, call `list_blocks` / `get_block_schema` — don't invent a type. --- ## 2. Anti-patterns — the mistakes that actually fail generation | ❌ Don't | ✅ Do | Why | |---|---|---| | `children` on `heading`/`image`/`button`/etc. | Keep leaf elements childless | Validator rejects it | | `iconListItem`/`accordionItem` at root or under `section` | Only inside `iconList`/`accordion` | Child-only; validator rejects | | Invent a type like `"hero"`, `"card"`, `"column"` | Compose from `section`/`container`/`block` | Only registry types render | | Literal `gap: "24px"` | `gap: "var(--theme-space-lg)"` | Literals freeze layout off the theme scale | | `maxWidth: "1100px"` + `margin auto` on a `container` | Nothing — leave it; only set `maxWidth` to go narrower/wider | `container` is already centered at the theme width; the redundant value overrides the theme token and sticks as a default in the inspector | | Light text with no background color set | Set `style.background.color` on the band, or dark text | The classic invisible-text bug (contrast lint) | | Invent `--theme-brand-primary` etc. | Use names from `get_theme_tokens`, always with hex fallback | Unknown vars resolve to nothing | | `display: "grid"` expecting 3 columns by default | Set `gridTemplateColumns` explicitly | Empty grid = single `auto` column | | `layoutMode: "simple"` on a horizontal row / grow-flex item | `layoutMode: "advanced"` | Wrong mode hides Inspector controls | | Dynamic element (`addToCartButton`) on a `home`/`single_page` template | Only on its compatible surface (see `get_template_guide`) | Renders placeholder otherwise | | `pageTitle` / `pageContent` inside a **Page** | `heading` / `richText` with the copy written in | Rejected on save. They bind to the page entity and exist only on the `single_page` TEMPLATE; the Page builder hides them from the palette, so the merchant could never edit what you emitted | | Recreating the global header/footer inside a page/template schema | Leave them out — the site renders the theme header/footer automatically | Duplicated, misaligned chrome | | Raw HTML in a page's text `content` to fake a layout | Use `pages_set_builder_schema` (builder mode) — never the text field | The text field is not a design surface; it won't render as a designed page | | Empty `children: []` / placeholder-less output | Fill with real placeholder content | Empty sections look broken | | Per-page colors/fonts in `style` everywhere | Lean on theme tokens; only override intentionally | Keeps the theme cascade working | **`layoutMode` rule of thumb:** emit `"advanced"` for any horizontal row, any grow/shrink flex item, or any grid beyond plain columns. Emit `"simple"` only for a plain column stack or a plain columns-grid. When in doubt → `"advanced"`. --- ## 3. Dynamic content — tags vs dedicated elements Both are valid and coexist: - **Dedicated Dynamic elements** (`productTitle`, `productPrice`, `postFeaturedImage`, …) — richer Inspector controls, friendlier. Prefer these when a dedicated element exists for the field. - **Primitive + entity tag** — `heading` with `text: "{product_title}"`, `richText` with `{post_content}`. Handy for inline text and inside query loops. Tags resolve against: (a) the ambient entity on a matching **template type**, or (b) the current row inside a `queryLoop` on that data source. Outside both → placeholder text. - Iteration metadata inside a loop: `{{loop.index}}`, `{{loop.first}}`, `{{loop.count}}`. - The legacy data-source-agnostic `{{item.*}}` set is **removed** — do not emit it. - Price tags (`{product_price}`, `{product_sale_price}`, `{product_original_price}`) are **pre-formatted** in the site currency — do NOT add a `|currency` filter. Call `get_dynamic_tags` (optionally scoped by context: product/post/course/page) for the live list, and `list_dynamic_filters` for filters (`truncate`, `date`, `default`, `upper`, `image_url`, …). --- ## 4. Query Loop (repeat-self model) The element holding `queryLoop` is the **template that gets repeated N times**. 🔴 **`queryLoop` sits at the ELEMENT ROOT, next to `props` / `style` / `children` — never inside `props`.** Zod strips unknown keys, so `props.queryLoop` is accepted with a green save and then silently dropped: the loop never runs and the block renders exactly once. Nothing warns you. (Measured 2026-09-03; this guide and `examples/templates/shop-catalog.json` both had it wrong, so every schema generated from them lost its loops.) Shape: `{ enabled, dataSource, limit, orderBy, categoryId? }` — `enabled` MUST be `true`, `dataSource` is one of `posts|products|courses|bundles|pages`, `orderBy` one of `date_desc|date_asc|title_asc|title_desc|price_asc|price_desc`. There is no `source` key. Typical: ``` container[display:grid, gridTemplateColumns:"repeat(3,1fr)"] └─ block{ queryLoop: { enabled:true, dataSource:"products", limit:6, orderBy:"date_desc" } } ← repeated once per product ├─ productImage / image+{product_image} ├─ heading + {product_title} └─ productPrice / {product_price} ``` Do not emit one block per item by hand. One template block + `queryLoop` → renderer expands it. For a dynamic carousel: a `slider` with ONE `queryLoop` child. For masonry: a grid container with `style.layout.masonry: true` wrapping the loop block. Call `get_data_contexts` for available sources. --- ## 5. Template type gating — what belongs where Every page is rendered through a **template type**. Dynamic elements are gated to compatible types; some elements are forbidden in some types. Before generating any template, call **`get_template_kit(type)`** — ONE call returns the guide (purpose, required/recommended/forbidden, layout, notes) + every relevant block schema + suggested patterns + critical rules. Don't fan out into many `get_block_schema` calls. Quick map (full detail via `get_template_guide`): | Template type | Renders | Key dynamic elements allowed | |---|---|---| | `home` | Landing / homepage | Recent-items loop subset (title/image/price/excerpt) | | `tenantPage` | **A tenant Page** (`pages_set_builder_schema`) — the ordinary case | none: write the copy straight into `heading` / `richText` | | `single_page` | The theme TEMPLATE wrapped around every page | page* (title/content/featured image) | | `single_post` | One blog post | post* (title/content/featured/excerpt/meta) | | `post_archive` / `post_category` / `search` | Post lists | post* in a loop | | `single_product` | One product | full product* set incl. addToCart, variants, quantity | | `shop_catalog` / `product_category` | Product lists | product* loop subset | | `single_bundle` | A product bundle | product* + checkout helpers | | `single_course` | One course | course* + curriculum | | `course_archive` | Course list | course* loop subset | | `checkout` | Checkout flow | checkout* family (items/form/shipping/payment/summary) | | `header` / `footer` | Site chrome fragments | logo, menu, searchBar, socialIcons; site_* shortcodes | Interactive single-entity elements (`addToCartButton`, `productVariants`, `productQuantity`, `productDescription`, `postContent`) need a single ambient entity → only on `single_*`, never in a recent-items grid on `home`. --- ## 6. Mobile, responsive & theme tokens (REQUIRED) ### Mobile & responsive — do not skip Every page is viewed on phones first. `style` is keyed by breakpoint and cascades: **`desktop` is the base; `tablet` and `mobile` are partial overrides.** Breakpoints: **tablet ≤1024px**, **mobile ≤767px**. You only repeat the keys that change. Checklist for every page you generate: - **Stack multi-column grids.** Any `gridTemplateColumns` with >1 column MUST add a `mobile` override to `"repeat(1, 1fr)"`, and usually a `tablet` step too (e.g. 4→2→1, 3→2→1, 2→1). - **Reduce big section padding on mobile.** Section `paddingTop`/`paddingBottom` of `var(--theme-space-section)` (96) is too tall on phones — drop to `var(--theme-space-xl)` (32) or `-lg` (24) at `style.mobile.layout`. - **Let type auto-scale.** Set `style.desktop.typography.fontSize` and leave `tablet`/`mobile` **empty** — it auto-scales 0.9× (tablet) / 0.85× (tablet→mobile) / 0.75× (desktop→mobile). Only set a `mobile` fontSize for a non-proportional size. `rem`/`em` desktop values cascade as-is (follow browser zoom, no auto-scale). - Switch a flex `row` to `direction: "column"` on mobile when items get cramped. ```json { "type": "container", "style": { "desktop": { "layout": { "display": "grid", "gridTemplateColumns": "repeat(3, 1fr)", "gap": "var(--theme-space-xl)", "paddingTop": "var(--theme-space-section)" } }, "tablet": { "layout": { "gridTemplateColumns": "repeat(2, 1fr)" } }, "mobile": { "layout": { "gridTemplateColumns": "repeat(1, 1fr)", "paddingTop": "var(--theme-space-xl)" } } } } ``` ### Standardize with theme tokens — don't hardcode Pull every standardizable value from a theme token — `var(--token, #fallback)`. Always include a fallback; never invent token names (unknown names silently fall back). Call `get_theme_tokens` for the live values. | Dimension (CSS) | Token family | |---|---| | `gap` / `rowGap` / `columnGap`, `padding*` | `--theme-space-xxs…-section` | | `borderRadius` | `--theme-radius-button` / `-card` / `-pill` / `-sm` / `-md` | | `boxShadow` / elevation | `--theme-shadow-xs` / `-sm` / `-md` / `-lg` | | `fontSize` | `--theme-text-display-xl/lg/md` · `-title-lg/md` · `-body-md/sm` · `-caption` | | `lineHeight` | `--theme-leading-tight` / `-normal` / `-relaxed` | | `color` / `backgroundColor` / `borderColor` | `--theme-ink` / `-body` / `-muted` / `-primary` / `-on-primary` / `-canvas` / `-surface-card` / `-surface-dark` / `-on-dark` / `-hairline` | | `fontFamily` | `--theme-heading-font` / `--theme-body-font` | | container `maxWidth` | `--theme-container-width` | - **Spacing scale:** `--theme-space-xxs`(4) `-xs`(8) `-sm`(12) `-md`(16) `-lg`(24) `-xl`(32) `-xxl`(48) `-section`(96). Default body gaps → `var(--theme-space-lg)`. Literal px only for intentionally out-of-scale values (`0px`, `2px` hairline). - A card should pull `borderRadius: var(--theme-radius-card)` + `boxShadow: var(--theme-shadow-sm)` + `backgroundColor: var(--theme-surface-card, #fff)` rather than ad-hoc values. --- ## 7. Generation workflow (recommended order) 1. Identify the **surface** from the request. Building one concrete page a merchant asked for → `tenantPage`. Building a reusable theme slot → its template type (`home` for the site landing, `single_page` for the wrapper rendered around every page, etc.). 2. `get_template_kit(type)` — one call for guide + schemas + patterns + rules. 3. Look at a **full-page example** for the closest topic: `list_examples` → `get_example(name)`. 4. Assemble: `section` → `container` → content, top to bottom. Fill placeholders with real copy. 5. Use tokens for spacing/color; set background on dark bands. 6. **Submit directly** via the save tool (`pages_set_builder_schema` / `kits_set_builder_schema` / `themes_set_template`) — pass the schema as an object. It validates + lints in one call. 7. If it returns errors or contrast warnings, fix and resubmit. Done when it saves clean. (Only use `validate_schema` to preview something you are not yet saving — it runs the same gate.) **Field caps that bite** (the Zod gate, not style advice): `style.*.background.color` and `border.color` are capped at **50 characters** — `var(--theme-x, #hex)` fits, a long `color-mix(in srgb, var(--theme-x, #hex) 15%, transparent)` does NOT (use `rgba()` or drop the fallback). `boxShadow` 200, `gradientCss` 2000, `border.radius` 100, typography fields 100. `layout.flexGrow` / `flexShrink` / `order` are **numbers**, not strings. `heading` requires `text` even when `mode: "rich"` (keep it as the plain-text fallback next to `richText`). ### Editing an existing page: `set` vs `patch` - **Creating a page, or replacing its whole layout** → `pages_set_builder_schema` (full schema). - **Tweaking a page that is already builder-type** (recolor a section, add one FAQ item, remove a block, reorder) → `pages_patch_builder_schema`. Send only the delta by element `id` instead of regenerating the entire JSON — far less to write, much faster. 1. `pages_get` to read current element `id`s and the layout `version`. 2. Send `ops` (applied in order): `update {id, set}` (deep-merge; `null` deletes a key), `replace {id, element}`, `insert {element, parentId?, index?|before?|after?}`, `remove {id}`, `move {id, parentId?, …}`. A later op may reference an id an earlier op created. 3. Pass `baseVersion` = the `version` from step 1 so a concurrent edit can't be clobbered. The patch validates the FULL resulting tree (same gates as `set`) and saves nothing if anything fails. --- ## 8. Resource index (call these, don't memorize) | Need | Resource | |---|---| | Full narrative spec | `builder-spec.md` (knowledge file) | | Machine spec (props/defaults/categories) | `builder-spec.json` + `get_block_schema(type)` | | All types | `list_blocks` (optional `category`) | | One element's controls | `get_element_controls(type)` / `get_control_options` | | Per-template rules | `get_template_guide(type)` | | One-shot template bundle | `get_template_kit(type)` | | Section UI patterns | `list_patterns` → `get_pattern(name)` | | **Full-page & per-template examples** | `list_examples` → `get_example(name)` | | Theme tokens | `get_theme_tokens` | | Dynamic tags / filters | `get_dynamic_tags`, `list_dynamic_filters` | | Loop data sources | `get_data_contexts` | | Nesting rules | `get_nesting_rules` | | Typography slots / responsive | `get_typography_slots`, `get_responsive_typography_rules` | | Header config | `get_header_settings` | | Save (validates + lints) | `pages_set_builder_schema` / `kits_set_builder_schema` / `themes_set_template` | | Incremental edit by id | `pages_patch_builder_schema` (update/replace/insert/remove/move; pass `baseVersion`) | | Preview-only validate | `validate_schema` (same gate as save; accepts object or string) | > Maintenance note: this guide is intentionally thin so it doesn't drift. Detailed truth lives in > `template-rules.ts`, per-element `meta.ts`, and `builder-spec.json`. If a rule here contradicts > those, those win — fix this file. ## Element types usable on this template ### layout - `section` (container) — Struktur/pembagi halaman (flex column) - `container` (container) — Kontainer 1100px terpusat - `block` (container) — Flexbox lebar penuh (flex column) ### dynamic - `productTitle` — Nama produk — terisi otomatis dari konten saat halaman dirender. - `productPrice` — Harga produk — otomatis tampilkan harga sale jika sedang berlaku. - `productDescription` — Deskripsi produk — terisi otomatis dari konten. - `productImage` — Thumbnail produk — single image (untuk gallery, pakai Product Image Gallery). - `productVariants` — Pemilih varian produk (pills / dropdown / list) — sinkron dengan Add to Cart & Quantity. - `productPlans` — Pemilih paket untuk produk digital (multiple plans) — sinkron dengan Add to Cart. - `productQuantity` — Stepper qty (− 1 +) untuk Add to Cart. Otomatis mengikuti stok varian. - `productCustomerNote` — Input catatan opsional dari customer. Nilainya ikut ke keranjang saat Add to Cart. - `addToCartButton` — Tombol Add to Cart / Buy Now. Sinkron dengan varian & qty terpilih; di dalam Query Loop jadi tambah-langsung atau tautan ke halaman produk. - `productStock` — Indikator status stok (Tersedia / Stok Terbatas / Habis). Bisa diatur tampil selalu atau hanya saat stok habis. Otomatis update saat varian dipilih. - `productSku` — Kode SKU produk — otomatis update saat varian dipilih. - `productSalePercent` — Badge diskon — "−25%" atau "Hemat Rp 50.000". Otomatis tersembunyi jika tidak sedang sale. - `productSalePrice` — Harga diskon saja — otomatis tersembunyi jika produk tidak sedang sale. - `productRegularPrice` — Harga asli (sebelum diskon) — bisa dicoret otomatis saat produk sedang sale. - `productCountdown` — Countdown sampai jadwal sale produk berakhir. Otomatis tersembunyi jika produk tidak punya sale aktif. - `productShare` — Tombol share produk ke WhatsApp, Facebook, Twitter/X, Telegram, atau salin link. - `productWholesaleTable` — Tabel harga grosir per kuantitas. Otomatis update sesuai varian terpilih. ### basic - `heading` — Judul teks (H1 - H6) - `basicText` — Paragraf teks polos tanpa toolbar - `richText` — Konten teks dengan heading - `video` — Embed video YouTube, Vimeo, atau Bunny.net - `image` — Gambar (URL atau dari Media Library) - `button` — Tombol dengan link - `textLink` — Teks dengan hyperlink - `icon` — Tampilkan satu icon dengan opsi link, frame, atau background - `divider` — Garis pemisah antar konten - `spacer` — Ruang kosong vertikal ### general - `slider` (container) — Carousel dengan panah & dot — tiap anak jadi slide - `logo` — Logo situs — auto dari Settings, atau image/text kustom - `accordion` (container) — Konten expandable dengan pertanyaan & jawaban - `breadcrumb` — Trail navigasi: Home > Section > Page (auto JSON-LD) - `counter` — Angka statistik dengan animasi count-up saat masuk viewport - `iconList` (container) — Daftar item (Item List) dengan icon, title, deskripsi & highlight badge - `countdownTimer` — Hitung mundur menuju tanggal & waktu tertentu - `alertBanner` — Kotak notifikasi atau pengumuman (info/success/warning/error) - `mapEmbed` — Embed Google Maps untuk lokasi toko - `searchBar` — Form pencarian — inline, overlay, atau icon expand. - `menu` (container) — Header all-in-one: logo, items dengan dropdown/mega, plus tombol Login/Register/Account/Cart - `htmlEmbed` — Embed kode HTML kustom atau dari AI - `cart` — Icon keranjang + panel cart slide-in (off-canvas kanan) - `miniCheckout` — Checkout compact 1 produk/bundle di landing page - `accordionItem` (container) — Satu panel di dalam Accordion — punya judul + konten nested (drop zone) - `iconListItem` — Satu item di dalam Icon List — punya icon, title, deskripsi & style lengkap - `brand` — Logo / nama brand di dalam Navigation - `menuItem` (container) — Satu item navigasi di dalam Navigation (bisa submenu / mega) - `customMega` (container) — Dropdown mega berisi element bebas, di dalam sebuah Menu Item - `menuAction` — Satu action header (login / cart / search / dll) di dalam Navigation ### ecommerce - `productImageGallery` — Galeri foto produk (Featured + Gallery) ## Element reference — props for every type Use these to build with the FULL element set (not just section/container/block/heading). Each entry lists the element's `props`. **Layout/visual CSS** (display, gap, padding, color, background, border, …) does NOT go here — it lives in `style.{bp}.layout.*` / `style.{bp}.typography.*` etc. (see the guide above). Dynamic elements (productTitle, postContent, …) only resolve in their compatible template type or a query loop. ### layout #### `section` · can have children Struktur/pembagi halaman (flex column) Props: - `htmlTag` — select "div"|"section"|"article"|"aside"|"nav"|"header"|"footer"|"main"|"a"|"figure"|"address"|"ul"|"ol"|"li" — HTML Tag — default: "section" - `layoutMode` — radio "simple"|"advanced" — default: "simple" #### `container` · can have children Kontainer 1100px terpusat Props: - `htmlTag` — select "div"|"section"|"article"|"aside"|"nav"|"header"|"footer"|"main"|"a"|"figure"|"address"|"ul"|"ol"|"li" — HTML Tag — default: "div" - `layoutMode` — radio "simple"|"advanced" — default: "simple" #### `block` · can have children Flexbox lebar penuh (flex column) Props: - `htmlTag` — select "div"|"section"|"article"|"aside"|"nav"|"header"|"footer"|"main"|"a"|"figure"|"address"|"ul"|"ol"|"li" — HTML Tag — default: "div" - `layoutMode` — radio "simple"|"advanced" — default: "simple" ### dynamic #### `productTitle` Nama produk — terisi otomatis dari konten saat halaman dirender. Props: - `level` — 'h1'-'h6' - `alignment` — 'left'|'center'|'right' - `linkToProduct` — boolean #### `productPrice` Harga produk — otomatis tampilkan harga sale jika sedang berlaku. Props: - `showOriginalOnSale` — boolean - `saleColorScheme` — 'primary'|'destructive'|'inherit' - `alignment` — 'left'|'center'|'right' - `size` — 'sm'|'md'|'lg'|'xl' - `outOfStockLabel` — string (shown in place of price when the selected variant is sold out) - `variantPriceDisplay` — 'auto'|'base' (auto = min–max range before selection; base = always base price) - `showShippingDiscountBadge` — checkbox — Tampilkan badge Potongan Ongkir #### `productDescription` Deskripsi produk — terisi otomatis dari konten. Props: - `plainText` — boolean #### `productImage` Thumbnail produk — single image (untuk gallery, pakai Product Image Gallery). Props: - `aspectRatio` — 'auto'|'1:1'|'4:3'|'3:2'|'16:9' - `fit` — 'cover'|'contain' - `preset` — 'productMain'|'cardMedium'|'cardWide'|'thumbnail'|'hero' - `linkToProduct` — boolean #### `productVariants` Pemilih varian produk (pills / dropdown / list) — sinkron dengan Add to Cart & Quantity. Props: - `style` — 'pills'|'dropdown'|'list'|'grouped' - `label` — string (empty hides) - `showPrice` — boolean - `showStockState` — boolean - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productPlans` Pemilih paket untuk produk digital (multiple plans) — sinkron dengan Add to Cart. Props: - `style` — 'cards'|'list' - `label` — string (empty hides) - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productQuantity` Stepper qty (− 1 +) untuk Add to Cart. Otomatis mengikuti stok varian. Props: - `label` — string - `minQuantity` — number - `maxQuantity` — number (0 = no cap, defer to stock) - `size` — 'sm'|'md'|'lg' - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productCustomerNote` Input catatan opsional dari customer. Nilainya ikut ke keranjang saat Add to Cart. Props: - `label` — string - `placeholder` — string - `maxLength` — number (1-1000) - `rows` — number (textarea rows) #### `addToCartButton` Tombol Add to Cart / Buy Now. Sinkron dengan varian & qty terpilih; di dalam Query Loop jadi tambah-langsung atau tautan ke halaman produk. Props: - `label` — string - `mode` — 'cart'|'buy-now' - `showIcon` — boolean - `iconName` — Lucide icon name (default 'ShoppingCart') - `size` — 'sm'|'md'|'lg' - `variant` — 'primary'|'secondary'|'outline' - `outOfStockLabel` — string - `needsSelectionLabel` — string - `needsPlanLabel` — string (digital multi-plan) - `subscribeLabel` — string (subscription) - `contactLabel` — string (service/booking) - `preorderLabel` — string - label saat produk dalam mode pre-order (tombol TETAP aktif) - `comingSoonLabel` — string - label sebelum jendela pre-order rilis dibuka - `soldOutLabel` — string - label saat kuota pre-order event habis - `chooseOptionsLabel` — string - label saat tombol ada DI DALAM kartu Query Loop dan produknya butuh pilihan (varian / paket digital / PWYW / langganan / service). Dalam keadaan itu tombol berubah jadi TAUTAN ke halaman produk, bukan tombol mati (default: 'Pilih Opsi'). - `width` — DEPRECATED legacy — for sizing set style.layout.width ('fit-content' for Auto, '100%' for Full, or any length for Custom). Defaults to Full. Pre-migration data only; do NOT seed on new elements. - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productStock` Indikator status stok (Tersedia / Stok Terbatas / Habis). Bisa diatur tampil selalu atau hanya saat stok habis. Otomatis update saat varian dipilih. Props: - `displayMode` — 'always'|'lowAndOut'|'outOfStockOnly' - kapan badge tampil. always = selalu; lowAndOut = hanya saat stok menipis/habis; outOfStockOnly = hanya saat habis. Keadaan pre-order yang memblokir pembelian (comingSoon/soldOut) tetap tampil di semua mode. - `inStockLabel` — string - `outOfStockLabel` — string - `lowStockLabel` — string - `lowStockThreshold` — number (0 = disabled) - `showCount` — boolean - `variant` — 'plain'|'badge'|'dot' - `size` — 'sm'|'md'|'lg' - `preorderLabel` — string - label saat produk dalam mode pre-order - `comingSoonLabel` — string - label sebelum jendela pre-order rilis dibuka - `soldOutLabel` — string - label saat kuota pre-order event habis - `showPreorderEta` — boolean - sambung estimasi kirim ke label saat pre-order - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productSku` Kode SKU produk — otomatis update saat varian dipilih. Props: - `prefix` — string (e.g. 'SKU:') - `hideIfEmpty` — boolean - `size` — 'sm'|'md'|'lg' - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productSalePercent` Badge diskon — "−25%" atau "Hemat Rp 50.000". Otomatis tersembunyi jika tidak sedang sale. Props: - `showAs` — 'percent'|'amount'|'both' - `variant` — 'badge'|'pill'|'plain' - `amountPrefix` — string - `hideWhenNoSale` — boolean - `size` — 'sm'|'md'|'lg' - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productSalePrice` Harga diskon saja — otomatis tersembunyi jika produk tidak sedang sale. Props: - `alignment` — 'left'|'center'|'right' - `size` — 'sm'|'md'|'lg'|'xl' - `hideWhenNoSale` — boolean - `outOfStockLabel` — string (shown in place of price when the selected variant is sold out) - `variantPriceDisplay` — 'auto'|'base' (auto = min–max range before selection; base = always base price) #### `productRegularPrice` Harga asli (sebelum diskon) — bisa dicoret otomatis saat produk sedang sale. Props: - `alignment` — 'left'|'center'|'right' - `size` — 'sm'|'md'|'lg'|'xl' - `strikethrough` — 'onSale'|'always'|'never' - `hideWhenNoSale` — boolean - `variantPriceDisplay` — 'auto'|'base' (auto = min–max range before selection; base = always base price) #### `productCountdown` Countdown sampai jadwal sale produk berakhir. Otomatis tersembunyi jika produk tidak punya sale aktif. Props: - `label` — string - `variant` — 'compact'|'boxes'|'inline' - `showDays` — boolean - `onExpired` — 'hide'|'showText' - `expiredText` — string - `labelDays` — string - `labelHours` — string - `labelMinutes` — string - `labelSeconds` — string - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productShare` Tombol share produk ke WhatsApp, Facebook, Twitter/X, Telegram, atau salin link. Props: - `label` — string - `platforms` — Array<'whatsapp'|'facebook'|'twitter'|'copy'|'telegram'|'linkedin'> - `variant` — 'icons'|'labeled'|'compact' - `iconSize` — number (px) - `shareText` — string template with {name} {url} - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. #### `productWholesaleTable` Tabel harga grosir per kuantitas. Otomatis update sesuai varian terpilih. Props: - `title` — string - `layout` — 'table'|'cards'|'inline' - `showSavings` — boolean - `qtyFormat` — 'min+'|'min' - `qtySuffix` — string (e.g. 'pcs') - `hideWhenNoTiers` — boolean - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new elements. ### basic #### `heading` Judul teks (H1 - H6) Props: - `text` — string — heading text (used when mode='plain') - `level` — 'h1'|'h2'|'h3'|'h4'|'h5'|'h6' (default: 'h2') - `mode` — 'plain'|'rich' (default: 'plain'). Rich enables inline HTML formatting per word. - `alignment` — DEPRECATED legacy text-align — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new headings. - `link` — optional URL string - `richText` — optional inline-only HTML string (span/strong/em/u/s/a + style color). Used when mode='rich'. No block tags. #### `basicText` Paragraf teks polos tanpa toolbar Props: - `text` — string — plain text (newlines render as
) - `tag` — 'div'|'p'|'span'|'figcaption'|'address'|'figure' (default: 'p') - `link` — optional URL string — wraps text content as - `wordsLimit` — optional integer — trim text to N words - `readMore` — optional string — appended after trimmed text when wordsLimit is set #### `richText` Konten teks dengan heading Props: - `body` — string — HTML content #### `video` Embed video YouTube, Vimeo, atau Bunny.net Props: - `url` — string — YouTube URL - `aspectRatio` — '16/9'|'4/3'|'1/1' (default: '16/9') - `title` — optional string #### `image` Gambar (URL atau dari Media Library) Props: - `src` — string — image URL or shortcode - `shape` — customRenderer — Bentuk - `aspectRatio` — optional 'auto'|'1:1'|'4:3'|'3:2'|'16:9' (default: 'auto') — crops image to a fixed ratio - `fit` — optional 'cover'|'contain' (default: 'cover') — object-fit when aspectRatio is not 'auto' - `linkType` — select "none"|"url"|"whatsapp" — Tipe Link — default: "none" - `htmlTag` — select "none"|"figure"|"div"|"custom" — HTML Tag — default: "none" - `metaTrackingEnabled` — checkbox — Aktifkan Meta Pixel — default: false - `tiktokTrackingEnabled` — checkbox — Aktifkan TikTok Pixel — default: false - `alt` — optional string — alt text - `link` — optional URL string - `mediaId` — optional string — media ID from R2 - `alignment` — optional 'left'|'center'|'right' #### `button` Tombol dengan link Props: - `text` — string — button label - `link` — string — href URL (default: '#'). Used when linkType='url'. - `linkType` — 'url' (default) | 'whatsapp' | 'checkout'. 'checkout' makes the button add the selected product to cart and go to /checkout. - `whatsappNumber` — string — international format, no '+' (e.g. '6281234567890'). Used when linkType='whatsapp'. - `whatsappMessage` — optional string — prefilled WhatsApp message. Used when linkType='whatsapp'. - `metaTrackingEnabled` — checkbox — Aktifkan Meta Pixel — default: false - `tiktokTrackingEnabled` — checkbox — Aktifkan TikTok Pixel — default: false - `productId` — string — product UUID to buy. REQUIRED when linkType='checkout'. Picker also caches productSlug/productName/productPrice/productType. - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new buttons. - `width` — DEPRECATED legacy — for sizing set style.layout.width ('fit-content' for Auto, '100%' for Full Width, or any length like '300px'/'20rem' for Custom). Pre-migration data only; do NOT seed on new buttons. - `icon` — optional IconValue { library: 'lucide'|'fa-solid'|..., icon: string } — leave unset for no icon - `iconPosition` — 'left'|'right' (default: 'right') — side of the text the icon sits on - `iconGap` — optional unit string e.g. '8px' — spacing between icon and text - `iconSize` — optional unit string e.g. '20px' (default 1em, follows button font-size) - `metaEvent` — optional string — Meta Pixel standard event e.g. 'Lead', 'Purchase' (linkType='url'/'whatsapp' only; checkout mode auto-fires AddToCart) - `tiktokEvent` — optional string — TikTok Pixel event (engagement mode only) #### `textLink` Teks dengan hyperlink Props: - `text` — string - `link` — string — href URL - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new links. #### `icon` Tampilkan satu icon dengan opsi link, frame, atau background Props: - `icon` — IconValue object — see icon_value_format above - `view` — 'default'|'stacked'|'framed' - `shape` — 'circle'|'square'|'rounded' - `link` — optional URL - `linkTarget` — '_self'|'_blank' - `primaryColor` — view-dependent → default: icon color • stacked: fill bg (icon glyph auto-contrast white) • framed: border + icon color. Empty = theme primary. - `secondaryColor` — framed bg only — IGNORED in default & stacked views. For a stacked dot just set primaryColor (e.g. '#2563eb'); do NOT set primaryColor='#ffffff' + secondaryColor='#2563eb' (renders invisible: white-on-white). - `size` — number (px) - `rotate` — number (degrees) - `borderWidth` — number (px, framed view) - `padding` — number (px) - `alignment` — DEPRECATED legacy — for positioning set style.layout.alignSelf ('Align Self': flex-start|center|flex-end|stretch). Pre-migration data only; do NOT seed on new icons. - `borderRadius` — number (px) #### `divider` Garis pemisah antar konten Props: - `height` — number — line thickness in px (default: 1) - `width` — string — CSS width e.g. '100%' (default: '100%') - `style` — 'solid'|'dashed'|'dotted'|'double' (default: 'solid') - `direction` — 'horizontal'|'vertical' (default: 'horizontal') - `alignment` — 'left'|'center'|'right' (default: 'center') - `color` — CSS color (default: '#e5e5e5') - `contentType` — customRenderer — Konten Tengah - `space` — number — vertical spacing in px (default: 16) - `icon` — optional Lucide icon name in center #### `spacer` Ruang kosong vertikal Props: - `height` — number — height in px on desktop (default: 48) - `mobileHeight` — optional number — height on mobile ### general #### `slider` · can have children Carousel dengan panah & dot — tiap anak jadi slide Props: - `htmlTag` — string — (undocumented) - `layoutMode` — string — (undocumented) - `perPage` — number — Slides / view (Desktop) — default: 4 - `perPageTablet` — number — Slides / view (Tablet) — default: 2 - `perPageMobile` — number — Slides / view (Mobile) — default: 1 - `gap` — unit — Gap — default: "24px" - `arrows` — checkbox — Tampilkan panah — default: true - `arrowsPosition` — radio "inside"|"outside" — Posisi — default: "inside" - `pagination` — checkbox — Tampilkan dot — default: true - `sliderType` — select "slide"|"loop"|"fade" — Tipe — default: "slide" - `autoplay` — checkbox — Autoplay — default: false - `interval` — number — Interval (ms) — default: 3000 - `speed` — number — Kecepatan transisi (ms) — default: 400 - `drag` — checkbox — Drag mouse/sentuh — default: true - `pauseOnHover` — checkbox — Pause saat hover — default: true - `arrowState` — radio "normal"|"hover" — default: "normal" - `arrowSize` — number — Ukuran tombol (px) — default: 40 - `arrowIconSize` — number — Ukuran ikon (px) — default: 16 - `arrowRadius` — unit — Radius (0 = persegi) — default: "9999px" - `arrowShadow` — select "none"|"soft"|"medium" — Shadow — default: "soft" - `arrowBg` — color — Background — default: "#ffffff" - `arrowIconColor` — color — Warna ikon — default: "#111827" - `arrowBorderWidth` — number — Border (px) — default: 1 - `arrowBorderColor` — color — Warna border — default: "rgba(0,0,0,0.08)" - `arrowBgHover` — color — Background - `arrowIconColorHover` — color — Warna ikon - `arrowBorderColorHover` — color — Warna border - `dotState` — radio "normal"|"active" — default: "normal" - `dotSize` — number — Ukuran (px) — default: 8 - `dotGap` — number — Jarak antar dot (px) — default: 8 - `dotRadius` — unit — Radius (0 = persegi) — default: "9999px" - `dotColor` — color — Warna — default: "rgba(0,0,0,0.2)" - `dotColorHover` — color — Warna :hover - `dotActiveColor` — color — Warna aktif — default: "rgba(0,0,0,0.7)" - `dotActiveScale` — number — Skala aktif — default: 1.2 #### `logo` Logo situs — auto dari Settings, atau image/text kustom Props: - `sourceMode` — optional 'auto'|'image'|'text' (default: 'auto') - `linkType` — optional 'home'|'url'|'none' (default: 'home') - `loading` — optional 'eager'|'lazy' (default: 'eager') - `src` — optional string — image URL when sourceMode='image' - `mediaId` — optional string — media ID from R2 - `alt` — optional string — alt text for the logo image - `text` — optional string — text logo, or fallback when auto-mode has no tenant logo - `inverseSrc` — optional URL — inverse logo shown on scrolled headers - `inverseMediaId` — optional string - `height` — optional string e.g. '40px' — required for SVG sources - `width` — optional string e.g. '120px' — required for SVG sources - `link` — optional URL — used when linkType='url' #### `accordion` · children: accordionItem only Konten expandable dengan pertanyaan & jawaban Props: - `titleTag` — 'div'|'h1'..'h6' (default: 'h3') — HTML tag for each panel title, shared - `icon` — IconValue — toggle icon shown COLLAPSED (shared across items) - `iconExpanded` — IconValue — toggle icon shown EXPANDED (shared) - `iconPosition` — 'left'|'right' (default: 'right') - `independentToggle` — boolean — allow multiple panels open at once (default: false) - `transition` — number — open/close transition in ms (default: 200) - `faqSchema` — boolean — emit FAQPage JSON-LD (default: false) - `iconRotate` — number — degrees to rotate the icon when expanded (optional) #### `breadcrumb` Trail navigasi: Home > Section > Page (auto JSON-LD) Props: - `dataSource` — select "auto"|"manual" — Sumber Data - `items` — BreadcrumbItem[] — each: { id, label, url } (1-10 items) - `separator` — 'slash'|'arrow'|'chevron'|'dot' (default: 'chevron') - `showHomeIcon` — boolean — show Home icon on first item (default: true) - `homeUrl` — string — URL for home link (default: '/') - `homeLabel` — text — Label Home - `showBlogArchive` — checkbox — Sertakan langkah arsip Blog - `showCatalog` — checkbox — Sertakan langkah Katalog - `showCategory` — checkbox — Sertakan kategori post/product - `hideCurrentItem` — checkbox — Sembunyikan halaman saat ini - `color` — string — link color (CSS) - `activeColor` — string — last item (current page) color - `separatorColor` — string — separator char color #### `counter` Angka statistik dengan animasi count-up saat masuk viewport Props: - `countFrom` — number — Mulai Dari - `countTo` — number — Sampai - `duration` — number — Durasi Animasi (ms) - `prefix` — optional string e.g. 'Rp' - `suffix` — optional string e.g. '+' - `thousandSeparator` — checkbox — Tampilkan separator ribuan - `separatorText` — text — Karakter Separator - `count` — number - `label` — string - `alignment` — 'left'|'center'|'right' (default: 'center') #### `iconList` · children: iconListItem only Daftar item (Item List) dengan icon, title, deskripsi & highlight badge #### `countdownTimer` Hitung mundur menuju tanggal & waktu tertentu Props: - `mode` — 'fixed'|'evergreen' (default: 'fixed'). 'fixed' counts down to targetDate; 'evergreen' gives each visitor their own countdown of evergreenSeconds, resumed across refreshes via localStorage - `targetDate` — string — datetime-local format e.g. '2026-12-31T23:59' (mode='fixed') - `evergreenSeconds` — number — per-visitor countdown length in seconds (default: 1200 = 20 minutes, mode='evergreen') - `evergreenOnEnd` — 'restart'|'expire' (default: 'restart') — loop into a new cycle, or stay on expiredText (mode='evergreen') - `showDays` — boolean (default: true) - `showHours` — boolean (default: true) - `showMinutes` — boolean (default: true) - `showSeconds` — boolean (default: true) - `expiredText` — string (default: 'Penawaran telah berakhir') - `layout` — 'boxes'|'minimal' (default: 'boxes') - `alignment` — 'left'|'center'|'right' (default: 'center') - `showLabels` — boolean (default: true) - `labelPosition` — 'above'|'below' (default: 'below') - `labelPlacement` — select "outside"|"inside" — Penempatan Label - `separator` — select "none"|"dash"|"colon"|"slash" — Separator - `boxBg` — CSS color (default: '#f3f4f6') - `boxBorderColor` — CSS color (default: '#e5e7eb') - `boxBorderRadius` — CSS string (default: '8px') #### `alertBanner` Kotak notifikasi atau pengumuman (info/success/warning/error) Props: - `title` — optional string - `message` — string — main message - `variant` — 'info'|'success'|'warning'|'error' (default: 'info') - `showIcon` — boolean (default: true) - `isDismissible` — boolean (default: false) - `alignment` — 'left'|'center'|'right' (default: 'left') #### `mapEmbed` Embed Google Maps untuk lokasi toko Props: - `embedUrl` — string — location keyword/address, coords, any Google Maps URL, embed URL,