Partials are the layout chrome — included by layouts/main.fluid(announcement-bar, top-bar, header, footer, quote-modal) andlayouts/embed.fluid (quote-modal only). They render with the layoutcontext, which carries the same properties as the page context pluscontent_for_layout, theme_css, theme_js.
announcement-bar.fluid
Renders: the thin announcement strip above the top bar — plain text or alink, with an optional custom background. Renders nothing unless enabledand the text is non-empty after | strip.
Data: Settings.AnnouncementEnabled, Settings.AnnouncementText,Settings.AnnouncementLink, Settings.AnnouncementBackground. (The rendereroverlays builder-edited Header.Announcement* values onto Settings beforerender, so the partial only reads Settings.)
Preserve: data-alyta-chrome="announcement" on the root — the builderpreview identifies chrome regions by data-alyta-chrome. Keep thedouble-gate (enabled + non-empty text).
footer.fluid
Renders: the site footer — logo, business name, address/phone/email(builder Footer.* values first, first location's details as fallback),social icon row, menu columns, copyright.
Data:- Footer.LogoUrl | default: Settings.Design.logoUrl | default: Settings.LogoUrl; Footer.BusinessName (else Organisation.Name); Footer.Address/Phone/Email (else firstLocation.Address/PhoneNumber/EmailAddress from Locations | first).- Social: Footer.{Facebook,Instagram,LinkedIn,Twitter,Youtube}Url each falling back to the same-named Settings.*, piped through | external_url (adds https:// to bare domains).- Menus: finds the menu with Name == "Footer" in Menus. Top-level items without children group into a single "Navigation" column (CSS columns:{{ Footer.LinkColumns | default: "1" }}); each item with children becomes its own column with the item label as heading. Only IsActive items render. All URLs pass through | storefront_url.
Preserve: data-alyta-chrome="footer"; the "Footer" menu-nameconvention; the flat-vs-children column algorithm (admins rely on it to shapecolumns); | external_url on social links and | storefront_url on menulinks.
header.fluid
Renders: the sticky site header — logo (linking to /t/), acheckbox-hack mobile nav toggle (no JS), the main nav with hover dropdownsfor menu items that have children, and the Login button.
Data:- Logo: Settings.Design.logoUrl | default: Settings.LogoUrl, else Organisation.Logo, else the org name as text.- Nav: the menu with Name == "MainMenu"; only IsActive items; items with Children.size > 0 render a nav-dropdown with caret + child links. When no MainMenu exists (or it's empty) a hard-coded fallback nav renders: Home (/t/), Locations (/t/locations), {{ Organisation.SpaceName | plural }} (/t/spaces).- Active state: {% if Request.Path == item1.Url %}class="active"{% endif %} (menu URLs are stored site-relative, Request.Path is the normalised request path).- Login: hidden by Header.HideLoginButton; label Header.LoginLabel (default "Login"); links to /account/login (an excluded path — served by the account app, not the theme; do not pass it through storefront_url, which deliberately skips /account).
Preserve: data-alyta-chrome="header"; the "MainMenu" menu-nameconvention; the #nav-check checkbox + label.nav-toggle pairing (pure-CSSmobile menu — theme.css keys off #nav-check:checked); | storefront_urlon menu URLs.
quote-modal.fluid
Renders: the site-wide "Get a Quote" modal — unit info panel, the form(name/email/phone/contact preference), error line, submit, and the successpanel. Hidden by default; opened by any .js-quote-btn anywhere on the page(cards, detail sidebar, embed inline view). Included at body level by bothlayouts — the embed layout carries its own copy because it omits main'schrome.
Data: Settings.RecaptchaSiteKey — when present emitswindow.__RECAPTCHA_SITE_KEY__ = '…', which submitQuote() uses to runreCAPTCHA Enterprise (action: 'getquote') before posting. Submits toPOST /api/public/spaces/{spaceId}/get-quote with{ name, email, phone, contactPreference, recaptchaToken }.
Preserve — element ids/classes theme.js requires: #quoteModal (rootoverlay; click-on-backdrop closes; opened with style.display='flex'),#quoteModalTitle (aria), .js-close-quote (✕ and Close buttons),#quoteUnitInfo (populated via DOM methods — deliberately no innerHTML),#quoteSpaceId (hidden input), #quoteForm, #quoteName, #quoteEmail,#quotePhone, #quoteContactPref (values Email/Phone), #quoteError,#quoteSubmitBtn, #quoteSuccess. Also the role="dialog"aria-modal="true" attributes and the window.__RECAPTCHA_SITE_KEY__ global.Body scroll is locked/unlocked by the open/close functions.
top-bar.fluid
Renders: an optional utility strip between the announcement bar and theheader, listing the links of the "TopBar" menu. Renders nothing whendisabled or when no TopBar menu with items exists.
Data: Header.TopBarEnabled (only an explicit false disables it — thedefault-on pattern is {% if Header.TopBarEnabled == false %} because unsetmust mean enabled); the menu with Name == "TopBar"; only IsActive items;URLs through | storefront_url.
Preserve: data-alyta-chrome="topbar"; the "TopBar" menu-nameconvention; the explicit == false disable check.
Quick contract checklist for a new theme
- [ ] Section wrapper loop with
data-alyta-section/data-alyta-type/alyta-hide-mobile/{% include 'section-style' %}in every section-rendering template. - [ ]
{% schema %}namematches the file base name; don't declare theoverride*settings yourself. - [ ] Components read the CSS variables (
--text-color,--bg-color,--button-bg, …) so section style overrides work. - [ ]
space-card: rootdata-location/category/price/duration/width/length- pipe-delimited
data-attributes;js-quote-btnwith its fivedata-*;js-view-spaceonly underEmbedInlineDetail; detail links byitem.UId.
- pipe-delimited
- [ ]
space-listingids:filterSidebar,filterPills,categorySelect,attributeFilterGroup,attributeFilters,filterMobileToggle,filterCount,visibleCount,sort-select,spaceList,spacePagination,noFilterResults,resetAllFilters+js-filter-cb/js-reset-filters/js-toggle-mobile-filters. - [ ] Spaces page location dropdown id
locationSelectwith an explicit empty "All locations" option. - [ ] Space detail:
#pricingCardwithdata-space-id/data-space-uid/data-duration-type,#quantityInput,js-qty-btnwithdata-delta,#priceBreakdown,#bookOnlineBtn,js-detail-thumb+#detailHeroImagefor the gallery. - [ ] Carousels:
data-testimonial-carousel/data-carousel-track/data-carousel-prev/data-carousel-next+.testimonial-carditems. - [ ]
space-gridsection:data-space-gridroot withdata-items-per-page,data-space-grid-list,data-filter-category,data-filter-attribute,data-space-grid-pager,data-pager-prev/info/next. - [ ] Quote modal + contact form ids exactly as listed above;
window.__RECAPTCHA_SITE_KEY__when reCAPTCHA is configured. - [ ] Embed template:
{% assign EmbedInlineDetail = true %}+#embedSpaceDetailpanel; keep.js-back-to-spaces. - [ ] Menu names
MainMenu,Footer,TopBar; chrome regions taggeddata-alyta-chrome="header|footer|topbar|announcement". - [ ] Internal links via
/t/…or| storefront_url; external/social via| external_url; video iframes via| safe_video_url. - [ ] Test emptiness with
.size > 0— empty lists are truthy in Fluid.