Reuse blocks (partials)
Factor repeated markup into partials, pass parameters explicitly, and dispatch a page's content blocks with a single include.
Liquid has no macros. Reuse in Quilium is {% include %} plus a naming convention.
_contents, _articleMeta, _localeUrl, are the ones shown here._contents, _localeUrl) are conventions from the example site, not built-ins. See Built-in partials and helpers for what the engine actually provides.1. Create a partial
Put it at the site root, prefix the filename with an underscore, and include it by name without the extension.
{%- comment -%} _articleMeta.liquid — arg: article, withByline (boolean) {%- endcomment -%}
<p class="meta">
<time datetime="{{ article.date }}">{{ article.date | date: 'D MMMM YYYY' }}</time>
{% if withByline and article.author != blank %}<span>{{ article.author }}</span>{% endif %}
{% if article.readingTime != blank %}<span>{{ article.readingTime }} min</span>{% endif %}
</p>
Include it with named parameters:
{% include '_articleMeta', article: art, withByline: true %}
Open every partial with a comment naming its arguments. It is the only signature Liquid gives you.
2. Know that include shares scope
include runs the partial in the caller's scope. The partial sees content, page, globals, locale and everything the caller assigned, on top of the parameters you passed. That is deliberate: it is what lets an element partial read the current block without threading it through.
The cost is that assignments leak in both directions.
_langSwitcher reads variant, pass variant: '' explicitly on the calls that want the default, rather than relying on nobody having set it earlier.{% include '_langSwitcher', variant: 'dark' %}
...
{% include '_langSwitcher', variant: '' %} {# not just {% include '_langSwitcher' %} #}
3. Dispatch a page's content blocks
The one partial every site needs: it walks a section and renders each block through its own content-type template.
{%- comment -%} _contents.liquid — arg: section (array of blocks) {%- endcomment -%}
{%- for content in section -%}
{%- if content.template != blank -%}
{%- assign _file = content.template | replace: 'elements/', '' | prepend: 'elements/' -%}
{% include _file %}
{%- endif -%}
{%- endfor -%}
Call it once per drop zone, from the page-type layout:
{% include '_contents', section: page.contents['main'] %}
Two things make it work. {% include %} accepts a variable path, so the content-type's own template key decides which element renders. And because scope is shared, each element sees content set to the block being rendered, with no parameter to pass.
The replace then prepend pair makes the elements/ prefix idempotent, so the dispatcher works whether a content-type declares 'aLaUne.liquid' or 'elements/aLaUne.liquid'.
4. Write a helper partial
A partial does not have to render markup. It can compute a value, which is how you get helper functions in a language that has no function calls. Include it where the value belongs, and its output lands there.
{%- comment -%} _localeUrl.liquid — arg: path (a fixed internal path starting with /) {%- endcomment -%}
{%- assign _first = path | truncate: 1, '' -%}
{%- if path != blank and _first == '/' -%}
/{{ locale.slug }}{%- unless path == '/' -%}{{ path }}{%- endunless -%}
{%- else -%}
{{ path }}
{%- endif -%}
<a href="{% include '_localeUrl', path: '/newsletter' %}">{{ 'Subscribe' | _ }}</a>
Use tight whitespace control ({%- and -%}) throughout a helper like this one. A stray newline ends up inside your href.
| slice: 0 for the first character. In Quilium, slice is overridden — it chunks an array into groups, and handed a string it returns empty. This exact helper shipped with slice on five demo sites: the test fell through to the else branch every time, the partial returned the bare path, and no internal link carried its locale prefix — on the English site they all pointed at the French pages, in 200, for months. truncate: 1, '' gives the first character. The general lesson is in Liquid filters: a Quilium filter can shadow the LiquidJS one you have in mind, and the failure is an empty string, not an error.5. Choose between a partial and a content-type
They solve different problems. Getting this wrong produces either an unusable backoffice or a template full of hardcoded copy.
| Use | When |
|---|---|
| Partial | the markup repeats but its data comes from the caller: a card, a byline, a URL helper, site chrome |
| Content-type | an editor needs to place it, order it, and fill its fields, on a page of their choosing |
| Element that reads page data | the block is placed by an editor but its content comes from a page-type query, so it carries no fields of its own |
The third row is the one people miss. A detail page's title block, body block and related-items block are content-types with no fields: the editor decides where they sit, and each one renders part of page.custom.* resolved once at page level.
Related
Last updated September 6, 2026.