Nunjucks & Liquid Tricks for Eleventy, Shopify, Jekyll etc.
Table of Contents
First Things First #
Built-in filters #
Syntax highlighting in VS Code~editors #
.njk #
This is a modern fork of the original extension. It is specifically designed to solve the "11ty problem" where you mix Nunjucks and HTML.
Why it's better: It injects Nunjucks grammar directly into the standard HTML grammar. This means you get full Nunjucks highlighting and the editor still knows it's an HTML file, giving you better Emmet and CSS autocompletion.
No Config: It works out of the box without needing to manually map file associations in your settings.
.liquid #
Auto-formatting in VS Code~editors #
- 🧩 Install
/prettier/prettier-vscode (if not yet)
.njk #
- Install a compatible Prettier plugin for your project, for example:
npm i -D prettier-plugin-jinja-template
- It might be tricky to find a well-maintained Nunjucks plugin, but
jinja-templateworks just fine with.njkvia.htmloverride:
{
"plugins": ["prettier-plugin-jinja-template"],
"overrides": [
{
"files": ["*.njk", "*.html"],
"options": {
"parser": "jinja-template"
}
}
]
}
PRO TIP: If you already use /anydigital/bricks, it’s even easier. You can simply:
ln -s ./node_modules/@anydigital/bricks/.prettierrc.json
.liquid #
- 🧩 Install
/docs/storefronts/themes/tools/shopify-liquid-vscode (same extension for both highlighting and formatting)
How to prevent unclosed html tags from breaking auto-formatting? #
{% # prettier-ignore %} might not help with that, because the parser crashes on the "broken" HTML before it even reads the ignore command.
But there is a trick with "fake" {% if true %} condition:
{% if true %}<html>{% endif %}
<head>
...
</head>
{% if true %}<body>{% endif %}
...
{% if true %}</body><html>{% endif %}
This "fake conditional" trick is a clever way to bypass the Abstract Syntax Tree (AST) parsers used by formatters like Prettier. Since the formatter sees the tag wrapped in a logic block, it often treats the HTML as a string or a partial fragment rather than a structural error.
Tricks #
Create array #
{% capture _new_array %}
1
2
3
{% endcapture %}
{% assign _new_array = _new_array | strip | split: '\n' %}
Sort array by attribute #
Per official .njk documentation:
sort(arr, reverse, caseSens, attr)
Sort arr with JavaScript's arr.sort function. If reverse is true, result will be reversed. Sort is case-insensitive by default, but setting caseSens to true makes it case-sensitive. If attr is passed, will compare attr from each item.
But you can actually do this trick:
{% for item in array | sort(attribute='weight') %}
...
{% endfor %}
Include and render .md file w/o its Front Matter (11ty-only) #
{# first, get the raw content using `html` as plain-text engine #}
{% set _eval = "{% renderFile './YOUR_FILE.md', {}, 'html' %}" %}
{% set _raw_md = _eval | renderContent('njk') %}
{# then, remove the front matter using regex, and render using `md` #}
{{ _raw_md | replace(r/^---[\s\S]*?---/, '') | renderContent('md') | safe }}
PS: Beware: False Positives in .liquid #
In Liquid templating, a "false positive" usually occurs when a value you expect to be falsy (like an empty string or zero) is actually treated as truthy by the Liquid engine.
Unlike languages like JavaScript or Python, Liquid has very specific rules for truthiness.
The "Everything is Truthy" Rule #
In Liquid, only false and nil (null) are falsy. Every other value evaluates to true in a conditional statement.
Common Pitfalls:
| Value | Liquid Result | Why? |
|---|---|---|
"" (Empty String) |
Truthy | An empty string is still a "present" object. |
0 (Zero) |
Truthy | Numbers are always truthy, regardless of value. |
[] (Empty Array) |
Truthy | An empty collection is not nil. |
How to Avoid False Positives #
To prevent these values from triggering your if statements, you should check for size or specific content rather than just the variable itself.
The Right Way vs. The Wrong Way:
- Wrong:
{% if my_string %} This will always show if the string exists, even if empty. {% endif %}
- Right (Checking for content):
{% if my_string != blank %} This only shows if there is actual text. {% endif %}
- Right (Checking arrays/strings by size):
{% if my_array.size > 0 %} This ensures the list isn't empty. {% endif %}
The blank and empty Keywords #
Liquid provides special keywords to handle these cases gracefully:
blank: Returns true if the value isnil,false, an empty string, or a string containing only whitespace.empty: Returns true if the value is an empty string, empty array, or empty hash.
Note: These are often used with the
unlesstag or the!=operator to ensure you are working with meaningful data.
- Featured in
/categories/nunjucks-macros
- See also
/tricks/11ty
Template Bricks (Components) #
The package includes reusable Liquid templates in the bricks/ directory. These are useful for common web development patterns.
Base HTML Template (__html.liquid) #
A unified base HTML template that provides the essential document structure with built-in support for modern web best practices.
Features:
- HTML5 DOCTYPE with language attribute (defaults to
en, configurable viasite.lang) - UTF-8 charset and comprehensive viewport meta tag with
viewport-fit=coverfor notched devices - Dynamic title generation with site title suffix (title is stripped of HTML tags and separated with
|) - Favicon link (to
/favicon.ico) - Automatic stylesheet linking from
site.stylesarray - Inline styles from
site.inline_stylesarray (joined with newlines in a<style>tag) - Automatic script loading from
site.scriptsarray (withdeferattribute) - Inline module scripts from
site.inline_scriptsarray (joined with newlines in a<script type="module">tag) - Custom header content via
content_for_header - Google Tag Manager integration (automatically rendered via
_gtm.liquidtemplate for both<head>and<body>)
Usage:
{% capture page_content %}
<!-- Your page content -->
{% endcapture %}
{% render 'bricks/__html', site: site, title: title, content_for_header: content_for_header, body: page_content %}
Note: Google Tag Manager is automatically included in both <head> and <body> (via the _gtm.liquid template) when site.prod and site.gtm_id are set.
Variables:
body- The page content to be rendered inside the<body>tag (required)title- Page title (optional, will be stripped of HTML tags)site.title- Site title for the title suffixsite.lang- Language code (optional, defaults to'en')site.styles- Array of stylesheet URLs (optional)site.inline_styles- Array of inline CSS strings (optional)site.scripts- Array of script URLs (optional)site.inline_scripts- Array of inline JavaScript strings (optional)content_for_header- Custom HTML for the head section (optional)site.gtm_id- Google Tag Manager ID (optional)site.prod- Boolean flag for production environment (optional)
Navigation (_nav.liquid) #
A navigation template that renders a list of navigation links with proper accessibility attributes.
Parameters:
nav_pages- Array of navigation page objects withurlandtitlepropertiescurrent_url- The URL of the current page (used to setaria-current="page")
Usage:
{% render 'bricks/_nav', nav_pages: navPages, current_url: page.url %}
Example:
{% assign navPages = site.pages | where: "nav", true %}
{% render 'bricks/_nav', nav_pages: navPages, current_url: page.url %}
Output:
<nav>
<a href="/">Home</a>
<a href="/about" aria-current="page">About</a>
<a href="/contact">Contact</a>
</nav>
Compatibility: Compatible with Eleventy Navigation plugin.
Google Tag Manager (_gtm.liquid) #
A template for embedding Google Tag Manager scripts in your pages.
Parameters:
site.gtm_id- Your Google Tag Manager container ID (e.g.,GTM-XXXXXXX)site.prod- Boolean flag to enable GTM only in productionfor_body- Boolean flag (default:false). Whenfalse, renders the script tag for the<head>. Whentrue, renders the noscript fallback for the<body>.
Note: This template is automatically included when using __html.liquid. You only need to manually render it if you're not using that base template.
Manual Usage:
In your base template's <head>:
{% render 'bricks/_gtm', site: site %}
In your base template's <body> (right after the opening tag):
{% render 'bricks/_gtm', site: site, for_body: true %}
Example (Manual Integration):
<!DOCTYPE html>
<html>
<head>
{% render 'bricks/_gtm', site: site %}
</head>
<body>
{% render 'bricks/_gtm', site: site, for_body: true %}
<!-- Your content -->
</body>
</html>
Rendering Logic: The GTM script is only rendered when both site.prod is true and site.gtm_id is set. The template uses a capture block to strip whitespace from the output.