Klarity Theme Markup
Lightweight tags for rendering products, collections, categories, variables, and conditionals — without exposing backend internals.
Quickstart
Insert tags into any theme template or CMS page body. The engine expands tags and safely escapes variables.
<h3>Trending in [[ page.category ]]</h3>
[[# products category="snacks" limit=4 sort="price_desc" ]]
<a class="card" href="/products/[[ handle ]]" aria-label="[[ title ]]">
<img src="[[ image ]]" alt="[[ title ]]" />
<div class="title">[[ title ]]</div>
<div class="price">[[ price_formatted ]]</div>
</a>
[[^ products ]]
<p class="muted">No products to show yet.</p>
[[/ products ]]
Variables
[[ site.name ]]— your site title.[[ page.route ]]— e.g.,home,category,products.[[ page.category_parts.N ]]— URL parts after/category/. Example: /category/snacks/chips ⇒["snacks","chips"].[[ product.* ]]and top-level aliases inside loops:[[ title ]],[[ image ]],[[ price_formatted ]], etc.
<h1>Welcome to [[ site.name ]]</h1>
<p>You are viewing: [[ page.route ]]</p>
Conditionals
Use inline comparisons or truthiness.
[[# if compare_at_price is not "" ]]
<span class="sale">Sale: [[ price_formatted ]]</span>
[[/ if ]]
[[^ if in_stock is not "true" ]]
<span class="soldout">Sold out</span>
[[/ if ]]
Products Loop
- By category:
[[# products category="snacks" limit=8 sort="newest" ]] ... [[/ products ]] - By collection:
[[# products collection="summer" limit=4 ]] - By single handle:
[[# products handle="sea-salt-chips" ]] - By multiple handles:
[[# products handles="sea-salt-chips, chili-crisps" ]] - By search:
[[# products search="chips" limit=6 ]]
[[# products category="[[ page.category_parts.0 ]]" limit=4 sort="price_asc" ]]
<a href="/products/[[ handle ]]" class="card">
<img src="[[ image ]]" alt="[[ title ]]" />
<div class="title">[[ title ]]</div>
<div class="price">[[ price_formatted ]]</div>
</a>
[[^ products ]]
<p class="muted">Nothing here yet.</p>
[[/ products ]]
Carousel (Splide)
Render slides using your existing Splide styling. The engine won’t override your inner markup — it just builds the container and repeats the slide body.
<section id="products">
[[# products category is "chocolate" id="snack-rail" type="carousel" limit=4 per_page=3 gap="10px" arrows=true pagination=false autoplay=true interval=3000 breakpoints='{"1024":{"perPage":3},"640":{"perPage":2}}']]
<a class="card" href="/products/[[ handle ]]" aria-label="[[ title ]]">
<div class="tile-panel" style="background:url('[[image]]') center center no-repeat; background-size:cover;"></div>
</a>
<div class="details">
<h3><a href="/products/[[ handle ]]" aria-label="[[ title ]]">[[ title ]]</a></h3>
</div>
<div class="price"><span class="only">Only</span><br>[[ price_formatted ]]
[[# if compare_at_price is not "" ]]
<span class="discountedprice">[[compare_at_price]]</span>
[[/ if ]]
</div>
<button class="buy" onclick="window.location='/products/[[ handle ]]';">Buy Now</button>
[[^ products ]]
<p class="muted">No featured items right now.</p>
[[/ products ]]
</section>
Options include: id, per_page, gap, arrows, pagination, autoplay, interval, breakpoints (JSON).
Images (by Asset library ID)
Use your image IDs from your image asset library. The engine calls the API, resolving image URL, slug (caption-like), and alt. Works as a single image or a simple Splide carousel.
Single image
[[ image id=42 style="cover" ]]
[[ image id="99" alt="bag" style="contain" ]]
<!-- Advanced: custom wrapper -->
<figure class="product-figure">
[[ image id=42 alt="Classic Tee — blue" style="cover" ]]
<figcaption>Classic Tee (blue)</figcaption>
</figure>
id— asset ID.alt— overrides DB alt; if omitted, DB alt/slug are used when present.style—cover(default) orcontain; setsobject-fit.
Carousel of images by IDs
Accepts any of these list forms: [1,2,3], "[1, 2, 3]", "1,2,3".
[[# carousel image-id=[1,2,3] id="hero-rail" per_page=1 pagination=true arrows=true ]]
<!-- Inside, you can reference [[ url ]], [[ alt ]], [[ slug ]] for the current slide -->
<img src="[[ url ]]" alt="[[ alt ]]" style="width:100%;height:420px;object-fit:cover;border-radius:12px;" />
[[/ carousel ]]
Troubleshooting
Ensure your list is clean JSON/CSV (the parser now accepts
[1,2,3], "[1, 2, 3]", or "1,2,3"). Avoid stray characters like [3 at the start.
Pagination
Show a subset per page and build numeric links. Use page-limit in the products block to override limit. Links preserve current filters.
Basic Usage
<!-- Products: page-limit overrides limit -->
[[# products category="[[ page.category_parts.2 ]]" page-limit="12" sort="newest" ]]
<a class="card" href="/products/[[ handle ]]">
<img src="[[ image ]]" alt="[[ title ]]" />
<div class="title">[[ title ]]</div>
<div class="price">[[ price_formatted ]]</div>
</a>
[[^ products ]]
<p class="muted">No items found.</p>
[[/ products ]]
<!-- Numeric pagination (centered) -->
[[# if pagination.pages_count > 1 ]]
<nav class="pagination" aria-label="Pagination"
data-cur="[[ pagination.page ]]"
data-pages="[[ pagination.pages_count ]]"
data-per="[[ pagination.per_page ]]">
<span class="pages js-pager"></span>
</nav>
[[/ if ]]
Examples
- Show 3 per page:
page-limit="3" - Force via URL (debug): append
?limit=1 - Sort descending price:
sort="price_desc"
Debugging
Print the live pagination meta (same API call):
<div class="muted">
page=[[ pagination.page ]] of [[ pagination.pages_count ]],
per=[[ pagination.per_page ]], total=[[ pagination.total ]]
</div>
• If you see
per=24 unexpectedly, your template likely didn’t include page-limit on the actual rendered products block. Add ?limit=1 to the URL to confirm the pipeline.•
page-limit overrides any limit inside the same block.• Links keep existing query params (like
sort) and are centered by default here.
Operators
- Equality:
is,==,is not,!= - Numeric:
<,<=,>,>= - Set:
is in(comma list)
[[# if collection is in "featured,summer" ]]
<span class="flag">Featured</span>
[[/ if ]]
[[# if price_cents >= 5000 ]]
<span class="flag premium">Premium</span>
[[/ if ]]
Markup Roadmap
- Loops over collections/categories (e.g., list all collections).
- Built-in money/number filters (formatting helpers in markup).
- Inline image transforms (thumb sizes, aspect locks).
- Pagination blocks for category pages.
- Partial parameters (
[[> sections/card with="product"]]style). - Internationalization hooks for text snippets.