Every page of a site is drawn inside the page named root on that site: its first screen above the page, its second screen below. Change root once and every page changes. Pages themselves should hold only their own content — no header, no footer.
Ask first
- Which site?
rootis per site. - What goes in the header and footer? The logo (an uploaded image URL), the menu items and the pages they open, contact details, legal links. Use what the person gives; don't invent phone numbers or addresses.
- Does the site sell? Only then add a cart button (step 1). An account link makes sense when customers sign in.
- Does any section need its own band (a sub-menu for "Support" pages, a campaign banner)? That is a parent page (step 3).
Steps
1. Build Studio: the page named root
Find it first: repo_find on page with { "data.site": "<site>", "data.name": "root" }. If it exists, read it with repo_find_one and edit it; never make a second one.
Otherwise create it with repo_create on page:
| Field | Value |
|---|---|
name / slug | root |
site | the site name |
appType | web |
screens | two screens, in this order: { "id": "header", "name": "Header", "slug": "header", "order": 0, "html": "<header document>", "attributes": {}, "config": {} } and { "id": "footer", "name": "Footer", "slug": "footer", "order": 1, "html": "<footer document>", … } |
activeScreenId | header |
html | the header document again (Build Studio's mirror of the active screen) |
Each screen is a full document whose body is the fragment:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"></head>
<body>
<header class="site-hdr" id="site-hdr">
<a href="/" class="logo"><img src="https://…/logo.png" alt="Business name"></a>
<nav>
<a href="/about">About</a>
<a href="/services">Services</a>
<a href="/contact">Contact</a>
</nav>
<a href="/account" data-show="customer.signedIn">My account</a>
<a href="/login" data-hide="customer.signedIn">Sign in</a>
<button data-action="cart.toggle" aria-label="Open cart">
Cart <span data-bind="cart.count" data-format="parentheses"></span>
</button>
<button data-action="ui.drawer" data-payload='{"id":"menu","action":"toggle"}' class="menu-btn">Menu</button>
</header>
<wm-drawer id="menu" direction="right" width="320px">
<span slot="trigger" hidden></span>
<nav slot="content"><a href="/about">About</a><a href="/services">Services</a><a href="/contact">Contact</a></nav>
</wm-drawer>
</body>
</html>The footer screen's body holds the <footer> (give it an id, e.g. site-ftr; step 2's script waits for it).
- Always two screens. With one screen, ordinary pages draw all of it above the page and nothing below.
- No
<main>inroot. A root with one screen that contains<main>is treated as a standalone page, and the built-in login, account and checkout pages then skip it. - Menu links are plain
/sluglinks. Usewm-mega-menuor awm-drawerfor a menu; notwm-navigation, which the published site doesn't load. - A
wm-drawerneeds aslot="trigger"child (it may behidden) and aslot="content"child. Without them it never builds: its links show inline in the header and opening it only locks page scrolling. Every attribute and event is in Appmint components on a page. - No
onclickin root's screens. The header and footer are rebuilt element by element, which removeson…attributes (and sends a<style>in a screen's body as the text[object Object], which the browser only repairs after a hydration error). Drive the drawer withdata-action="ui.drawer"as above. - The cart button only appears on a site with Storefront (
e-commerce) or Rental switched on (Site Features). Leave it out otherwise. - The same footguns as any page apply: no
id="root", no literal</body>in the content, nodata-reactblocks (see Build a website page).
To change one screen of an existing root, send both screens back with repo_update (patch: { "screens": [header, footer], "html": "<header document>" }). page_write's html only writes the active screen, which is the header.
Hands on: root's id (sk).
Check: repo_find_one shows two screens, header first. Open /app/build-studio/root; the screen list shows Header and Footer.
2. Page settings → Style: the site's CSS, fonts and script
Shared styling and code go in root's Page settings → Style, not in its screens. Write them with page_write on root's id:
| Field | Holds |
|---|---|
css | the site's shared stylesheet (header, footer, typography, colours) |
styleLinks | font and CSS file URLs, e.g. a Google Fonts link |
javascript | the site-wide script (menu behaviour, sticky header), with the design-mode guard |
scriptLinks | URLs of uploaded script files, for anything long |
Why here and not in the screens:
- root's Style is loaded on every page of the site and on the built-in login, account and checkout pages (on those, only while the site has a My Account layout page or an
account-layoutpage; with neither, root's header shows there unstyled — see Built-in pages and templates). A<style>in a screen's<head>reaches ordinary pages but not those built-in pages, which draw the screens' bodies only. <script>inside root's screens never runs. The header and footer are rebuilt element by element and script tags in them are dropped. root's JavaScript runs fromstyle.javascript, joined with each page's own.
(function boot() {
if (document.body && document.body.getAttribute('data-design-mode') === 'true') return;
// The footer is drawn after the page: once it exists, the header and page do too.
if (!document.getElementById('site-ftr')) return void setTimeout(boot, 30);
if (document.body.dataset.siteBooted) return;
document.body.dataset.siteBooted = '1';
// …site-wide behaviour…
})();root's script runs on every page, joined into one <script> with the page's own and the site's tracking. A syntax error in it, or an error it throws while it runs (e.g. calling a method on an element that isn't on this page), stops all of them site-wide, including Google Analytics, the Pixel and the site's cookies, which come last. Check it parses, and guard every lookup, before writing. page_write appends: pass replaceStyle: true with the full script when changing it.
Hands on: nothing new.
Check: repo_find_one on root shows style.css and style.javascript at the top level (not under data.style).
Stop: root is on every live page. Before saving a new or changed root, show the person the header, the footer and the menu links, and wait for yes.
3. Build Studio: parent pages (optional)
A page can name a parent: another page on the same site whose screens[0] is drawn above it and screens[1] below it, inside root's header and footer:
root header → parent screen 1 → the page → parent screen 2 → root footerCreate the parent like root (two screens, no <main>), with any name (e.g. support-layout). Then set parent: "support-layout" on each page that should have it (repo_update, patch: { "parent": "support-layout" }). The parent's own Page settings → Style loads on those pages too.
Only one level is used: a parent's own parent is ignored for wrapping. parent: "root" means no parent.
Hands on: the parent page's name. Check: open one of those pages; the band sits between the site header and the page.
4. Site settings: the built-in pages
/login, /register, /forgot-password, /reset-password, /account, /checkout and /order/… are Appmint's own screens, not pages of the site. They are wrapped this way:
- The site's My Account → Layout page (
site.data.myAccount.layout; new sites getbase-layout) is looked up, thenaccount-layout. - From the first one that exists, the wrapper is the first usable of: the site's
root, that page's parent, the page itself. "Usable" means two screens, or one screen with no<main>.
So with a proper two-screen root, the built-in pages get root's header and footer and root's Style; the layout page is only used when the site has no usable root.
Feature pages are different. A switched-on feature at a URL the site has no page for (e.g. /blog, /book) is drawn inside root and account-layout together (account-layout as the parent). So:
- Give the site a page named
account-layout, two screens, holding no header or footer of its own — at most a spacer under a fixed header (<div class="hdr-spacer"></div>) and an empty footer screen. Without one, those feature pages show with no header or footer at all. - Don't copy root's header into
account-layoutor into a layout page; it would show twice on feature pages.
Hands on: nothing new.
Check: open /login and /checkout: root's header and footer are there, styled, once.
When it's done
Tell the person: root (header and footer, the menu links), where the shared CSS and script live, any parent pages and which pages use them, and that /login, /checkout and /account show the same header. Open root in Build Studio and the site's home page and /login.
What it can't do yet
- A page can't opt out of root. Every page of the site gets it. A page that must stand alone (a print view, an embed) needs a different site.
- One parent level only.
- Studio's Page settings for root are root's own; there is no separate "site CSS" screen. root's Style is the site's CSS.
- Feature pages without
account-layoutlose the header. The fallback is that literal page name, not the My Account layout page.
Don'ts
- Don't put the header and footer into each page. They would show twice under root.
- Don't make a second
rooton the same site, and don't rename it. - Don't put
<script>in root's screens, or the site CSS only in a screen's<head>. - Don't put
<main>in root or in a layout page. - Don't add a cart button to a site that has no Storefront or Rental feature.
- Don't use
wm-navigationfor the menu.