Nunjucks / Liquid HTML blades for 11ty/Build Awesome, Jekyll, etc.
Table of Contents
Install
Install HTML blades via npm
npm install @anydigital/blades
cd ./_includes # your includes dir
ln -s ../node_modules/@anydigital/blades/_includes/blades
That's it! Now you can use HTML blades in your templates like this:
In Nunjucks:
{% extends 'blades/html.njk' %}
{% include 'blades/gtm.njk' %}
In Liquid:
{% include blades/html.liquid %}
{% include blades/gtm.liquid for_body=true %}
{% render blades/links, links: site.links, current_url: page.url %}
All CSS and HTML blades above are available as a Jekyll theme:
Install as Jekyll theme
In you _config.yml:
remote_theme: anydigital/blades@v0.27.0-beta # or latest
plugins:
- jekyll-remote-theme
Living example: /anydigital/bladeswitch/blob/main/_config.yml
Or use a preconfigured template:
🥷 Bladeswitch Starter ↗ Jekyll ⁺ Pico ⁺ Blades
Base HTML
Usage in Nunjucks layout:
{% extends 'blades/html.njk' %}
{% block body %}...{% endblock %}
Living example: /anydigital/build-awesome-starter/blob/main/_includes/default.njk
How it works
<!doctype html>
<html lang="{{ site.lang | d('en') }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover" />
<link rel="icon" href="{{ site.favicon | d('/favicon.ico') }}" />
<title>
{{- title | string | striptags ~ ' | ' if title -}}
{{- site.title -}}
</title>
<meta name="description" content="{{ summary }}" />
{%- for href in site.styles %}
<link rel="stylesheet" href="{{ href }}" />
{%- endfor %}
<style>
{{ site.inline_styles | d([]) | join('\n') }}
</style>
{%- for src in site.scripts %}
<script src="{{ src }}" defer></script>
{%- endfor %}
<script type="module">
{{ site.inline_scripts | d([]) | join('\n') }}
</script>
{{ content_for_header }}
{% include 'blades/gtm.njk' %}
</head>
<body>
{% set for_body = true %}{% include 'blades/gtm.njk' %}
{% block body %}
{% endblock %}
</body>
</html>
title | stringavoids error with| striptagswhen you pass a pure number.
Usage in Liquid layout:
{% capture body %}...{% endcapture %}
{% include blades/html.liquid %}
Living example: /anydigital/bladeswitch/blob/main/_includes/default.liquid
How it works
<!doctype html>
<html lang="{{ site.lang | default: 'en' }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover">
<link rel="icon" href="{{ site.favicon | default: '/favicon.ico' }}">
<title>
{%- if title %}{{ title | strip_html | append: ' | ' }}{% endif -%}
{{- site.title -}}
</title>
<meta name="description" content="{{ summary }}">
{%- for href in site.styles %}
<link rel="stylesheet" href="{{ href | relative_url }}">
{%- endfor %}
<style>
{{ site.inline_styles | join: '\n' }}
</style>
{%- for src in site.scripts %}
<script src="{{ src }}" defer></script>
{%- endfor %}
<script type="module">
{{ site.inline_scripts | join: '\n' }}
</script>
{{ content_for_header }}
{% include blades/gtm.liquid %}
</head>
<body>
{% include blades/gtm.liquid for_body=true %}
{{ body }}
</body>
</html>
Links
Usage with Eleventy Navigation plugin:
{% assign links = collections.all | eleventyNavigation %}
{% render blades/links, links: links, current_url: page.url %}
Usage inside Pico's Navar:
TBD
How it works
<ul>
{%- for link in links %}
<li>
<a href="{{ link.url }}" {% if link.url == current_url %}aria-current="page"{% endif %}>
{{- link.title -}}
</a>
</li>
{%- endfor %}
</ul>
Sitemap 🆕
/anydigital/blades/blob/main/_includes/blades/sitemap.xml.njk
Google Tag Manager (GTM)
Usage in Nunjucks:
...
{% include 'blades/gtm.njk' %}
</head>
<body>
{% set for_body = true %}{% include 'blades/gtm.njk' %}
...
Living example: /anydigital/blades/blob/main/_includes/blades/html.njk
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>.
How it works
{% if site.prod and site.gtm_id %}
{% if not for_body %}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ site.gtm_id }}');</script>
<!-- End Google Tag Manager -->
{% else %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ site.gtm_id }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endif %}
{% endif %}
Usage in Liquid:
...
{% include blades/gtm.liquid %}
</head>
<body>
{% include blades/gtm.liquid for_body=true %}
...
Living example: /anydigital/blades/blob/main/_includes/blades/html.liquid
How it works
{% if site.prod and site.gtm_id %}
{% capture _ %}
{% unless for_body %}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ site.gtm_id }}');</script>
<!-- End Google Tag Manager -->
{% else %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ site.gtm_id }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endunless %}
{% endcapture %}
{{ _ | strip }}
{% endif %}
<!--prettier-ignore-->
PS: Nunjucks tricks
Syntax highlighting in VS Code~editors
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.
Auto-formatting in VS Code~editors
-
🧩 Install
/prettier/prettier-vscode (if not yet)
-
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/blades, it’s even easier. You can simply:
ln -s ./node_modules/@anydigital/blades/.prettierrc.json
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 in 11ty
{# 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: Liquid tricks
Syntax highlighting & auto-formatting in VS Code~editors
- 🧩 Install
/prettier/prettier-vscode (if not yet)
- 🧩 Install
/docs/storefronts/themes/tools/shopify-liquid-vscode
(2-in-1 extension for highlighting & formatting)
This is a huge advantage for .liquid over .njk so far.
Create array
{% capture _new_array %}
1
2
3
{% endcapture %}
{% assign _new_array = _new_array | strip | split: '\n' %}
Beware: False Positives in .liquid
❌ 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 %}
- Featured by:
- See also: