Developer docs  /  Themes

Partials reference

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 %} name matches the file base name; don't declare the override* settings yourself.
  • [ ] Components read the CSS variables (--text-color, --bg-color, --button-bg, …) so section style overrides work.
  • [ ] space-card: root data-location/category/price/duration/width/length
    • pipe-delimited data-attributes; js-quote-btn with its five data-*; js-view-space only under EmbedInlineDetail; detail links by item.UId.
  • [ ] space-listing ids: 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 locationSelect with an explicit empty "All locations" option.
  • [ ] Space detail: #pricingCard with data-space-id / data-space-uid / data-duration-type, #quantityInput, js-qty-btn with data-delta, #priceBreakdown, #bookOnlineBtn, js-detail-thumb + #detailHeroImage for the gallery.
  • [ ] Carousels: data-testimonial-carousel / data-carousel-track / data-carousel-prev / data-carousel-next + .testimonial-card items.
  • [ ] space-grid section: data-space-grid root with data-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 %} + #embedSpaceDetail panel; keep .js-back-to-spaces.
  • [ ] Menu names MainMenu, Footer, TopBar; chrome regions tagged data-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.