Header / Footer Builder¶
The Header / Footer Builder lets a merchant compose a storefront header and
footer visually, with no code. The builder lives in the Shop Dashboard
(Online Shop → Headers and Online Shop → Footers). What it produces is a
self-contained block of HTML + CSS that the storefront injects verbatim — so a
header/footer carries no runtime dependencies and renders identically everywhere.
How it is stored¶
A header or footer entity holds a settings object with three keys:
{
"config": { "...": "what the builder edits" },
"content": "<header>…</header>",
"styles": ".ch-scope … { … }"
}
config— the structured model the builder reads and writes.content— generated HTML, scoped under.ch-scope(header) /.cf-scope(footer).styles— generated CSS, namespaced to the same scope so it never leaks into the page.
content and styles are always generated from config on save. Never edit
them by hand — edit config (via the builder) and they recompile.
The storefront renders from content + styles only.
Announcement bar (above the header)¶
A header can carry a slim strip rendered above the header bar. It is a
single optional announcement object on the header config — not an element, so
it does not belong to a zone:
Like the header itself it has three columns, each an ordered list of elements:
{
"announcement": {
"enabled": true,
"background": "#111827",
"color": "#ffffff",
"size": "normal",
"sticky": false,
"elements": {
"left": [{ "id": "p1", "type": "phone", "label": "Order:", "number": "+8801700000000" }],
"center": [
{ "id": "r1", "type": "rotate", "interval": 4,
"messages": ["Free delivery over ৳2,000", "Cash on delivery nationwide"] },
{ "id": "t1", "type": "text", "text": "Use EID40", "strong": true, "motion": "flash", "url": "/sale" }
],
"right": [{ "id": "s1", "type": "social", "links": [
{ "network": "facebook", "url": "https://facebook.com/yourshop" },
{ "network": "whatsapp", "url": "https://wa.me/8801700000000" }
]}]
}
}
}
Element types¶
type |
Fields | Notes |
|---|---|---|
text |
text, url?, strong?, motion? |
motion is none | flash | fade | slide-up |
rotate |
messages[], interval, url? |
Text slider — cycles in place. Messages share one grid cell, so the width never jumps |
marquee |
text, speed, direction, url? |
speed = seconds per pass (floored at 4); direction is left (right-to-left) or right |
phone |
number, label? |
Renders a tel: link with a phone icon |
social |
links[] of { network, url } |
facebook | instagram | twitter | youtube | whatsapp. Entries with a blank URL are skipped |
- Omit the object, or set
enabled: false, and nothing at all is emitted — headers authored before this existed compile byte-for-byte as before. sizeiscompact(30px) |normal(38px) |tall(48px).- When the bar and the header are both sticky, the compiler offsets the header by the bar's height so the two stack instead of overlapping.
- Legacy single-message bars keep working. A config with
text/url/linkLabel/alignand noelementsis migrated on read into onetextelement in the column itsalignchose, and renders as it always did.
Everything animates in pure CSS¶
A compiled header carries no JavaScript, so every moving part above is a CSS keyframe animation. That is a real constraint — but it also means they keep working with JS disabled and cost nothing at runtime.
It is also why there is no close button: a dismissal could not be
remembered and the bar would return on every page. For a closeable bar use the
announcement-bar page widget, which is a React component.
All motion is disabled under @media (prefers-reduced-motion: reduce) — a
permanently scrolling bar is an accessibility problem (WCAG 2.2.2), so a
shopper whose OS asks for less motion sees the first message, static.
There is no close button here, by design. A compiled header is HTML plus
scoped CSS with no JavaScript, so a dismissal could not be remembered and the
bar would return on every page. For a closeable bar use the announcement-bar
page widget — that one is a React component and remembers the choice per
shopper.
| Use | When |
|---|---|
Header announcement |
The message belongs to the site — shipping terms, a hotline — and should sit above the header on every page |
announcement-bar widget |
The message belongs to one page's content, or needs to be dismissible |
Header model — zones and elements¶
A header has three zones — left, center, right — and each zone holds an
ordered list of elements.
{
"elements": {
"left": [
{ "id": "logo-1", "type": "logo", "logoType": "text", "text": "Aurora", "height": 32 }
],
"center": [
{
"id": "menu-1",
"type": "menu",
"variant": "plain",
"source": "static",
"gap": 28,
"items": [
{ "label": "Home", "url": "/" },
{ "label": "Shop", "url": "/shop" }
]
}
],
"right": [
{ "id": "search-1", "type": "search", "variant": "icon", "placeholder": "Search products…", "categories": [] },
{ "id": "act-1", "type": "action", "action": "account" },
{ "id": "act-3", "type": "action", "action": "cart" },
{ "id": "btn-1", "type": "button", "variant": "solid", "label": "Sale", "url": "/shop?sort=sale", "background": "#e11d48", "color": "#ffffff" }
]
}
}
Element types: logo, menu, search, button, text, text-link,
icon-link, image, action (account / wishlist / cart), phone.
A menu element can be "source": "static" (custom links) or "source":
"dynamic", bound to a shop menu by menuSlug — dynamic menus stay in sync with
the menu manager.
Footer model — flexible column grid and widgets¶
A footer is a grid of columns, and each column holds an ordered list of
widgets. The number of columns is the merchant's choice via columnCount.
{
"columnCount": 4,
"cols": [
{
"id": "col-1",
"elements": [
{ "id": "h-1", "type": "heading", "text": "Aurora" },
{ "id": "t-1", "type": "text", "text": "Quality products, delivered fast." },
{ "id": "s-1", "type": "social", "facebook": "https://facebook.com/aurora", "instagram": "https://instagram.com/aurora" }
]
},
{
"id": "col-2",
"elements": [
{ "id": "h-2", "type": "heading", "text": "Shop" },
{ "id": "l-1", "type": "links", "links": [
{ "label": "All products", "url": "/shop" },
{ "label": "New arrivals", "url": "/shop?sort=new" }
] }
]
},
{
"id": "col-4",
"elements": [
{ "id": "n-1", "type": "newsletter", "title": "Newsletter", "placeholder": "Enter your email", "buttonLabel": "Subscribe" }
]
}
],
"copyright": "© {year} Aurora. All rights reserved."
}
Widget types: heading, text, links, menu (static or dynamic shop
menu), image, newsletter, social, html (raw HTML escape hatch).
Choosing the column count¶
columnCount controls the grid:
0(or omitted) — responsive auto-fit; columns size themselves to the content.1–6— that many equal-width columns per row.
The grid automatically collapses to 2 columns on tablet (≤ 768px) and a single column on mobile (≤ 480px).
copyright renders as a bottom bar beneath the grid. {year} is replaced with
the current year. Leave it empty to hide the bar.
Authoring guidelines¶
- Header: keep it light — a logo, one menu, search, and a couple of action icons. More than ~5 elements in a zone tends to wrap on smaller screens.
- Footer: 3–5 columns reads best. Lead with a brand column (heading + text + social), follow with link columns, and end with a newsletter.
- Colors: set
background,color, andmutedColoron the footer config. Link and social colors derive frommutedColorand brighten tocoloron hover. - Images: logo and footer image widgets store a media slug in
src. The builder resolves it to a full media URL at compile time — do not paste absolute URLs. - Set as default: use the Set as default action to make a header/footer the storefront default for all pages.
Sample presets¶
Complete, ready-to-use samples (full { config, content, styles }) ship in the
theme kit:
10-theme-preset-kit/src/themes/custom/header-footer/sample-header.json10-theme-preset-kit/src/themes/custom/header-footer/sample-footer.json
To reuse one, create a header/footer in the builder and seed its settings with
the sample config, then save — the builder regenerates content and styles.