Developer docs  /  Themes

Sections reference

attributes.fluid

Renders: a grid of clickable feature tiles ("Features to suit every need")built from the org's attribute catalogue. Each tile links to/t/spaces?attributes={id}. Default section on the Features page(templates/attributes.fluid page key attributes); addable to anysection-driven page.

Data: Attributes (context; Id, Name, SpaceCount),Section.Settings: heading, subheading, attributeIds (typeattributes picker — curates which features show and their order; empty =all), showCounts (checkbox), background (default/alt). Blocks of typeicon (attributeId — type attribute picker, icon — named key fromschema/attribute-icons.json, image — custom upload; image wins over iconkey, both win over the automatic keyword match). maxBlocks: 20.

Preserve: delegates each tile to {% include 'attribute-tile' %} with theloop variable named Attribute (the snippet reads Attribute andSection.Blocks from the enclosing scope). Selected-ids ordering pattern:loop Section.Settings.attributeIds outer, Attributes inner, so admin pickorder is display order. page-section / page-section--alt classes drive thetinted-background option.

category-tabs.fluid

Renders: "Space Categories" — a column grid of category cards (via thecategory-card snippet), each showing icon, name and from-price.

Data: Categories (context), Section.Settings: heading,categoryIds (type categories picker; empty = all, picked order = displayorder), columns (2/3/4, default 4).

Preserve: grid grid--cols-{n} class convention; loop variable itembefore {% include 'category-card' %}.

contact-form.fluid

Renders: a lead-capture contact form (name, email, phone, message) postingto POST /api/public/contact. Default (only) section of the Contact page(ThemeV2SectionDefaults.CreateContactSections()).

Data: Section.Settings: heading, subheading. Submission handledentirely by theme.js (submitContactForm).

Preserve — element ids theme.js binds to (all required):contactForm (form; submit handler attached on DOMContentLoaded),contactName, contactEmail, contactPhone, contactMessage (inputs readby id), contactError, contactSuccess (toggled via style.display),contactSubmitBtn (disabled + relabelled during submit). Because these areids, only one contact-form section can exist per page.

cta.fluid

Renders: a full-width call-to-action banner (heading, line, one primarybutton). Ships in the default home layout.

Data: Section.Settings: heading, subheading, buttonText,buttonLink (default /t/spaces), alignment (left/center/right).Falls back to Organisation.SpaceName copy when settings are empty.

Preserve: cta-section cta-section--{alignment} classes; btn-primarybutton class (styled by the CSS-variable button tokens, including theper-section overrideButtonColor).

faq.fluid

Renders: an accordion FAQ list using native <details>/<summary> — noJS involved.

Data: Section.Settings: heading, subheading. Blocks of typequestion (question text, answer textarea). maxBlocks: 20, fourdefaultBlocks seeded.

Preserve: the <details class="faq-item"><summary class="faq-question">structure (theme.css styles the marker/expansion). No hooks.

featured-space.fluid

Renders: a single highlighted space card, wide or tall.

Data: Spaces (context), Section.Settings: heading, spaceId (typespace picker; falls back to Spaces | first when unset or when the pickedspace isn't in the current list), layout (row/column).

Preserve: it assigns {% assign item = featuredSpace %} then{% include 'space-card' %} — the full space-card contract (below) applies.The featured-space--column modifier drives the tall layout.

features-list.fluid

Renders: intro text on the left (heading, description, optional arrowlink) and a stacked list of image+text feature rows on the right. Hard-codedfallback rows (24/7 Security, Easy Access, Flexible Pricing) render when thesection has no blocks.

Data: Section.Settings: heading, description, image, title(both currently unused in markup beyond schema presence), linkText (blankhides the link), linkUrl (default /t/spaces). Blocks of type feature(title, description, image). maxBlocks: 12, three defaultBlocks.

Preserve: features-list-layout / features-list-intro /features-list-items / features-list-item(__img/__placeholder/__text)class structure. No JS hooks.

features-metrics.fluid

Renders: a row of big stat counters ("500+ Happy Customers"). Fallbackmetrics (including a live {{ Locations.size }} count) render with no blocks.

Data: Section.Settings: heading. Blocks of type metric (value,label). maxBlocks: 8, four defaultBlocks. Context: Locations (fallbackonly).

Preserve: metrics-grid / metric-item(__value/__label) classes. Nohooks.

features-table.fluid

Renders: a comparison table — one column per category, optionalstarting-price row, then one row per feature block with tick/cross/textcells.

Data: Categories (context — the column headers), Section.Settings:heading, description, showPrice (checkbox → price row usingitem.Price | dollar + DurationType). Blocks: feature rows whereitem.Settings.feature is the row label and item.Settings.values is alist whose entries are matched positionally to categories; the literalstrings "true"/"false" render tick/cross SVGs, anything else renders astext. (Note: the schema JSON doesn't declare the blocks array — block datacomes from saved section settings; keep the values list convention.)

Preserve: comparison-table-wrap (horizontal scroll container),comparison-table, comparison-check / comparison-cross SVG classes, andthe positional values-to-Categories mapping.

gallery.fluid

Renders: up to six images in a masonry-ish gallery. Empty slots areskipped.

Data: Section.Settings: heading, subheading, image1image6(image) + image1Altimage6Alt (text).

Preserve: gallery / gallery__item classes, loading="lazy" onimages. No hooks.

google-reviews.fluid

Renders: a Google-branded reviews section in one of three styles —grid (cards + inline star summary), slider (carousel with drop-shadowcards), minimal (summary panel on the left, carousel on the right). Includes"Read all reviews" and "Write a review" CTAs derived from the Google listingURL.

Data: Section.Settings: style (grid/slider/minimal), heading,subheading, listingUrl (Google Maps place link; legacy fallbackreviewsUrl is also read — Section.Settings.listingUrl | default:Section.Settings.reviewsUrl), buttonText, averageRating (text, coercedwith | plus: 0), reviewCount (number), reviewLimit (range 1–12, default6), columns (grid style: 2/3/4). Blocks of type review (name,quote, rating range 1–5, timeAgo). maxBlocks: 12, threedefaultBlocks.

The place id is split out of listingUrl on query_place_id= to buildhttps://search.google.com/local/writereview?placeid={id}; the listing URLitself (through | external_url) is the read-reviews target.

Preserve:- Each card body comes from {% include 'google-review-card' %} with loop variable item (a review block).- Carousel contract for the two slider styles — identical to testimonials: a root with data-testimonial-carousel, a scroll track with data-carousel-track, and nav buttons with data-carousel-prev / data-carousel-next. initTestimonialCarousels() in theme.js binds to those exact attributes, scrolls by one .testimonial-card width + 24px, and disables buttons at the ends — so each carousel item must have class testimonial-card.- gr-section gr-style--{style} on the <section> and the gr-* classes (gr-summary, gr-stars, gr-star--empty, gr-cta) are styled in theme.css.- reviewLimit is enforced in the template with a shown counter — Fluid here has no limit: shortcut on for over blocks; keep the counter pattern.

hero.fluid

Renders: the top-of-home hero banner: <h1>, subheading, up to twobuttons, optional background image with an automatic dark tint(linear-gradient(rgba(0,0,0,0.5),…) over the image).

Data: Section.Settings: heading, subheading, buttonText,buttonLink (default /t/spaces), showButton1 (checkbox default true),buttonText2 (blank hides button 2), buttonLink2 (default /t/contact),showButton2, backgroundImage. Falls back toWelcome to {{Organisation.Name}} / space-name copy.

Preserve: hero section class + hero__actions; btn-primary /btn-outline button classes; keep the tint gradient when a background imageis set (the schema promises readable text). Note the guard style{% unless Section.Settings.showButton1 == false %} — an unset checkboxmust still show the button.

image-banner.fluid

Renders: a full-bleed background-image banner with overlay, heading,subheading and one button. Distinct from hero: fixed-height variants and anoverlay div instead of an inline gradient.

Data: Section.Settings: image, heading, subheading, buttonText(blank hides button), buttonLink (default /t/spaces), height(small/medium/large, default medium).

Preserve: image-banner image-banner--{height} classes, theimage-banner__overlay element (text readability), image-banner__content.

image-with-text.fluid

Renders: the classic 50/50 image-beside-text block with optional eyebrowtagline, button, side-swap and tinted background. Shows an SVG placeholderwhen no image is set.

Data: Section.Settings: image, imageAlt (falls back to heading),eyebrow, heading, body, buttonText (blank hides), buttonLink(default /t/spaces), reverse (checkbox → image-with-text--reverse),background (default/alt).

Preserve: image-with-text(__media/__content/__placeholder) structure andthe --reverse modifier.

locations-strip.fluid

Renders: "Our Locations" — a grid of location cards via thelocation-card snippet. Ships in the default home layout.

Data: Locations (context), Section.Settings: heading, locationIds(type locations picker; empty = all, picked order = display order),columns (2/3/4, default 3).

Preserve: loop variable item before {% include 'location-card' %};grid grid--cols-{n}.

logo-list.fluid

Renders: an "As featured in" strip of up to six brand logos. Empty slotsskipped.

Data: Section.Settings: heading, logo1logo6 (image),logo1Altlogo6Alt (text).

Preserve: logo-list / logo-list__item / logo-list__heading classes.No hooks.

map.fluid

Renders: "Find Us" — one embedded Google Map card per location that has aLatitude, with name/address/phone and a link to that location's spaces.Zero-config: locations without coordinates are skipped automatically.

Data: Locations (context; Latitude, Longitude, Name, Address,PhoneNumber), Organisation.SpaceName, Section.Settings: heading.

Preserve: the keyless embed URL formathttps://maps.google.com/maps?q={lat},{lng}&z=15&output=embed, iframeloading="lazy" + referrerpolicy="no-referrer-when-downgrade", and thelocation link pattern /t/locations/{{ item.Name | url_encode }}/spaces(the route resolver matches the location by name, spaces optionally replacedwith dashes).

multicolumn.fluid

Renders: up to three fixed columns of optional-image + title + text("Why customers choose us"). Columns without a title are skipped.

Data: Section.Settings: heading, subheading, column{1..3}Title,column{1..3}Text, column{1..3}Image.

Preserve: multicolumn / multicolumn__col / multicolumn__imageclasses. No hooks.

pricing-table.fluid

Renders: one pricing card per category — icon, name, price (or "Contactus"), dimensions list, and a "Browse {Category}" button linking to/t/spaces?category={{ item.Id }}.

Data: Categories (context; Icon, Name, Price, DurationType,Width, Length, Height, Id), Section.Settings: heading,subheading, featured (exact category name to highlight →pricing-card--featured).

Preserve: the ?category={id} link format (the spaces page'sserver-driven category filter reads it), pricing-cards /pricing-card(--featured) classes.

rich-text.fluid

Renders: a simple centred/left/right text block — eyebrow, heading, bodyparagraph, optional button. The generic "About us" workhorse.

Data: Section.Settings: eyebrow, heading, body, buttonText(blank hides), buttonLink (default /t/spaces), alignment(left/center/right, default center), background(default/alt).

Preserve: rich-text rich-text--{alignment} on the container,rich-text__eyebrow / rich-text__body classes (the eyebrow class is reusedby image-with-text).

size-guide.fluid

Renders: "What Size Do You Need?" — a grid of size explainer tiles (icon,name, what fits, dimension tag). Four hard-coded fallback tiles(Small/Medium/Large/Extra Large) render when the section has no blocks.

Data: Section.Settings: heading, subheading(both fall back to copy using Organisation.SpaceName). Blocks of typesize (title, description, example). maxBlocks: 6, threedefaultBlocks.

Preserve: size-guide-grid / size-guide-item(__icon/__example)classes. No hooks.

space-grid.fluid

Renders: a self-contained paged grid of space cards for content pages —optional client-side category/feature dropdown filters and Previous/Nextpaging. Unlike the main spaces listing (the space-listing snippet), this isa section, so multiple can exist on a page; all state lives inside thesection root, scoped per instance by initSpaceGridSections() in theme.js.

Data: Spaces (context), Section.Settings: heading, itemsPerPage(3/6/9/12, default 6), showFilters (checkbox), categoryIds(type categories — server-side narrowing by item.CategoryId),attributeIds (type attributes — narrowing by item.AttributeIds,match-any within the template loop).

Preserve — the data-attribute contract theme.js binds to:

<section class="page-section" data-space-grid
data-items-per-page="6"
data-show-filters="1">
...
<select data-filter-category>...</select> <!-- options filled by JS from card data-category -->
<select data-filter-attribute>...</select> <!-- options filled by JS from card data-attributes (| split) -->
<div class="space-list" data-space-grid-list> ... space cards ... </div>
<div class="space-grid-pager" data-space-grid-pager style="display:none">
<button data-pager-prev>...</button>
<span data-pager-info></span>
<button data-pager-next>...</button>
</div>
</section>

data-space-grid (root), data-items-per-page, data-space-grid-list,data-filter-category, data-filter-attribute, data-space-grid-pager,data-pager-prev, data-pager-info, data-pager-next are all requiredexactly as named. The JS reads each card's data-category andpipe-delimited data-attributes (provided by the space-card snippet), socards inside the grid must be real space-card includes with loop variableitem. Cards are shown/hidden with style.display — don't fight that withCSS.

testimonials.fluid

Renders: customer testimonials as a grid (2–4 columns) or anarrow-navigated carousel. Avatar is the first two letters of the name; fivestatic stars.

Data: Section.Settings: heading, subheading, layout(grid/carousel), columns (2/3/4, grid only). Blocks of typetestimonial (name, role, quote). maxBlocks: 9, threedefaultBlocks.

Preserve — carousel contract (shared with google-reviews):

<div class="testimonial-carousel" data-testimonial-carousel>
<button class="testimonial-carousel__nav testimonial-carousel__nav--prev" data-carousel-prev aria-label="Previous">&#8249;</button>
<div class="testimonial-carousel__track" data-carousel-track>
<div class="testimonial-card">...</div>
</div>
<button class="testimonial-carousel__nav testimonial-carousel__nav--next" data-carousel-next aria-label="Next">&#8250;</button>
</div>

data-testimonial-carousel, data-carousel-track, data-carousel-prev,data-carousel-next and the .testimonial-card item class are all read byinitTestimonialCarousels() (step size = card width + 24px; button disabledstate synced on scroll). testimonial-avatar / testimonial-name /testimonial-role / testimonial-quote classes are shared with thegoogle-review-card snippet.

trust-badges.fluid

Renders: a horizontal strip of trust signals (shield icon or uploadedimage + short label). Five hard-coded fallback badges render with no blocks.

Data: Section.Settings: heading (blank hides it). Blocks of typebadge (label, image — shield SVG fallback when absent).maxBlocks: 8, four defaultBlocks.

Preserve: trust-badges / trust-badge classes. No hooks.

video-embed.fluid

Renders: a responsive 16:9 YouTube/Vimeo iframe with optional heading andcaption; a play-icon placeholder when no URL is set.

Data: Section.Settings: heading, videoUrl, caption.

Preserve: the safe_video_url filter on the iframe src — it whitelistsyoutube/vimeo/dailymotion/loom hosts, forces HTTPS and renders about:blankotherwise. Never emit Section.Settings.videoUrl raw into an iframe.Also keep sandbox="allow-scripts allow-same-origin",referrerpolicy="no-referrer" and loading="lazy" on the iframe, and thevideo-embed(--placeholder) classes.

why-choose-us.fluid

Renders: a grid of icon feature cards (uploaded icon image or tick SVGfallback, title, description). Ships in the default home layout.

Data: Section.Settings: heading, subheading, columns(2/3/4, default 3). Blocks of type feature (title,description, icon image). maxBlocks: 9, six defaultBlocks.

Preserve: feature-card(__icon) classes, grid grid--cols-{n}. Nohooks.