inmake DOCS
Docs / Generative / Elements

Elements

The "elements" rule fragment — composed into the CRM & Studio agents' prompts and served as drop-core://rules/elements.

ELEMENTS — where behavior JavaScript (and element CSS) lives. An element (elements/{elementId}) is a reusable UI building block: fields (the data shape of ONE placement), html (a Handlebars fragment binding them), css/js, sample (preview data — it MUST validate against fields: new placements copy it, and writeStoreElement refuses a sample whose shape contradicts a declared kind). Documents place elements via kind:"element" fields and pages bind {{{ field.html }}} — an element edit propagates to every placement. This rule is the channel contract: WHERE a script is stored decides WHICH pages load it.

ELEMENT SCOPE — what an element IS and what it is NOT. An element is ONE section or component that MORE THAN ONE page could place: one root, a handful of named fields, its data shape readable at a glance. Before creating one, check the library (an existing element that fits wins) — and check the other direction too: a section only ONE page will ever show (named after that page or its business content — why-us, payroll, certificates) is that page's own composition and belongs in the page's PER-DOCUMENT schema (general/{collection}/{document}) and its template (writeWebTemplate), not in the library. Shape rules: NO positional keys (t1 … tN, item1_title, col2_text) — a list or a table is an array of object items with named keys (a pricing table = tiers[] {name, price, features[]}); more than one visual section in one fragment (several <section>s, a hero + a grid + a CTA), a <main> or a whole page, or more than about 20 top-level fields → split it into one element per section, the shared card / item markup as a component: true element placed inside; no visitor-facing literal text, alt / aria-label strings or inline brand SVGs in the fragment (a field — icon for an SVG, image for a logo — or a translations key). IDS: no literal id="…" — the fragment must be safe twice on one page; an in-page anchor (the target of a menu #hash) is a BOUND field, id="{{anchor}}" (a text field the editor fills, unique on the page), or a data-anchor the script reads.

THE CODE CHANNELS — the renderer links each automatically; never hand-wire them:

  • ELEMENT-SCOPED — the element's own js/css (updateStoreDocument("elements/{id}", {js}), or writeElement/writeStoreElement where available). Served as /store/{sid}/js/element/{id}.js and linked ONLY on pages that place the element (deduped when placed twice). This is the DEFAULT home for behavior: a carousel, dialog, accordion, player, submit handler that belongs to one element.
  • SHARED ELEMENT RUNTIME — setup/elements js/css (updateStoreDocument("setup/elements", {js})). Served MERGED as ONE /store/{sid}/js/elements.js on every page that renders at least one element. For code two or more elements need: a reveal/entrance engine, one generic carousel/lightbox engine several elements wire to, the shared namespace of helpers. setup/elements accepts ONLY its js/css keys — order/categories/imports/assets are managed by the admin's Elements view and module installs, never written by hand.
  • KIT ASSET FILES — setup/elements.assets rows, present only on libraries that imported a platform kit before that import was retired (linked as /assets/{storage_path}?v={version} on element-bearing pages). Large imported libraries live here; you never write these rows, and nothing creates new ones.
  • TEMPLATE JS — a web template's js (writeWebTemplate) loads on pages rendered by that template. For page-specific behavior that is not element code.

MODULE ELEMENTS: an element that uses a store module (calls its Drop{Name} runtime or /m/{moduleId}/* routes) has a fifth, module-owned channel — the module runtime elements/{moduleId}_runtime, linked after the shared runtime on pages whose elements carry module. Never write it and never copy it; the contract (set module, call the runtime, customized-vs-fork) is the MODULE ELEMENTS rule — read module-elements first.

SERVICES: element JS that needs the store's OWN backend code (a /fn/{serviceId}/… route — listServices) fetches it directly from the element's own js; there is no runtime to go through. The contract is the SERVICES rule — read services first.

DECISION RULE: one element needs it → that element's js. Two or more elements need it → the shared runtime. One template's page needs it → that template's js. Third-party (analytics, fonts, external embeds) → setup/web.headTags with the absolute external URL. NEVER create per-behavior standalone store files (site/js/carousel.js, site/js/accordion.js, …) and never stack store-file <script src> tags in setup/web.headTags — that loads every behavior on every page, no matter what the page uses, and orphans the code from the elements it serves. The ONE store-authored <script> that belongs in headTags is a tiny INLINE first-paint bootstrap (below).

SCRIPT CONVENTIONS (both channels):

  • Emission order is fixed, at the END of the page body: page template JS → kit asset files → shared runtime → per-element scripts → shell (header/footer) scripts. The DOM is already parsed — do NOT wrap code in a DOMContentLoaded listener; a per-element script may rely on the shared runtime's namespace being defined, but page template JS runs FIRST, so it must not assume any element script has run (wire it to the shared runtime's re-entrant init(root?) instead).
  • Attribute-driven and idempotent: wire markup with a data attribute (<div class="el-carousel" data-el-carousel>), init by querying it, and guard double-init by stamping a …-ready attribute — the fragment can appear twice on one page, and previews may re-run scripts against injected markup.
  • Dependency-free vanilla JS. The shared runtime exposes ONE namespace (e.g. window.DropElements) with re-entrant init(root?) functions; element scripts may call into it.
  • Degrade gracefully: gate JS-dependent styling behind a class the script itself stamps on <html>, so a page whose script never loads renders static, not broken or blank.
  • FIRST-PAINT ARMING: end-of-body scripts can run after first paint, so when entrance/reveal styling must hide content BEFORE first paint, the class-arming snippet is the one legitimate inline <script> in setup/web.headTags — keep it tiny and pair it with a failsafe that un-arms if the runtime never reports in (content must render if the script dies).
  • Element css follows the CSS SCOPING rule (unique element namespace class on every selector); shared runtime CSS styles only the shared primitives it owns.

MIGRATING A LEGACY SETUP (a store with many headTags <script src> files): classify each file — used by one element → move the body into that element's js (drop any DOMContentLoaded wrapper); shared engine/runtime → merge into setup/elements.js; page-specific → the page template's js. Then rewrite headTags down to third-party tags + the inline bootstrap, verify every behavior on the rendered site, and delete the orphaned files. Kit asset rows whose hooks nothing uses anymore (setup/elements.assets from an old import) are removed via the admin's Elements view, not by editing the doc.