Content Stacks

Collect and render content from nested templates with push and stack tags

4 min read 753 words

Content stacks solve a common problem in template hierarchies: child templates and partials need to contribute CSS, JavaScript, or other content to a location they do not control (typically the<head> or end of <body>in a base layout).

With{% push %} and {% stack %}, any template can append content to a named collection, and the base template decides where that collected content is rendered.

Basic Usage

Use{% push "name" %} to add content to a named stack, and {% stack "name" %}to render everything that was pushed:

{# base.html #}
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="/css/main.css">
    {% stack "styles" %}
</head>
<body>
    {% block content %}{% end %}

    <script src="/js/main.js"></script>
    {% stack "scripts" %}
</body>
</html>
{# page.html #}
{% extends "base.html" %}

{% block content %}
    <div class="widget">
        {{ widget.render() }}
    </div>

    {% push "styles" %}
        <link rel="stylesheet" href="/css/widget.css">
    {% end %}

    {% push "scripts" %}
        <script src="/js/widget.js"></script>
    {% end %}
{% end %}

The rendered output places widget assets exactly where the base template defined the stack output points.

Named Stacks

Stack names are string literals. You can define as many independent stacks as you need:

{% stack "styles" %}
{% stack "scripts" %}
{% stack "meta" %}
{% stack "modals" %}

Each {% push %}targets a specific stack by name. Pushes to different stacks are independent:

{% push "meta" %}
    <meta property="og:title" content="{{ page.title }}">
{% end %}

{% push "styles" %}
    <style>.hero { background: url('{{ page.hero_image }}'); }</style>
{% end %}

Content is rendered in the order it was pushed. If multiple templates push to the same stack, the output follows rendering order.

Use with Inheritance

Stacks work across template inheritance chains. Child blocks can push content that appears in the base template's stack output:

{# base.html #}
<html>
<head>
    {% stack "head" %}
</head>
<body>
    {% block body %}{% end %}
    {% stack "scripts" %}
</body>
</html>
{# layout.html #}
{% extends "base.html" %}

{% block body %}
    <nav>...</nav>
    <main>{% block content %}{% end %}</main>

    {% push "head" %}
        <link rel="stylesheet" href="/css/layout.css">
    {% end %}
{% end %}
{# article.html #}
{% extends "layout.html" %}

{% block content %}
    <article>{{ article.body }}</article>

    {% push "head" %}
        <link rel="stylesheet" href="/css/article.css">
    {% end %}

    {% push "scripts" %}
        <script src="/js/reading-time.js"></script>
    {% end %}
{% end %}

The final output includes both /css/layout.css and /css/article.css in the <head>, and /js/reading-time.js before </body>.

Stacks also work inside included templates:

{# partials/gallery.html #}
<div class="gallery">
    {% for image in images %}
        <img src="{{ image.url }}" alt="{{ image.alt }}">
    {% end %}
</div>

{% push "styles" %}
    <link rel="stylesheet" href="/css/gallery.css">
{% end %}

{% push "scripts" %}
    <script src="/js/lightbox.js"></script>
{% end %}
{# page.html #}
{% extends "base.html" %}

{% block content %}
    {% include "partials/gallery.html" %}
{% end %}

The gallery partial's CSS and JS are collected and rendered at the stack output points in base.html.

Common Patterns

CSS Collection

Aggregate page-specific stylesheets without duplicating<link>tags in every child template:

{# base.html #}
<head>
    <link rel="stylesheet" href="/css/reset.css">
    <link rel="stylesheet" href="/css/main.css">
    {% stack "styles" %}
</head>
{# dashboard.html #}
{% extends "base.html" %}

{% block content %}
    {% push "styles" %}
        <link rel="stylesheet" href="/css/charts.css">
        <link rel="stylesheet" href="/css/dashboard.css">
    {% end %}

    <div class="dashboard">
        {% include "partials/chart.html" %}
    </div>
{% end %}

JS Modules

Load JavaScript at the end of the body, contributed by any template in the hierarchy:

{# base.html #}
<body>
    {% block content %}{% end %}

    <script src="/js/vendor.js"></script>
    {% stack "scripts" %}
    {% stack "inline_js" %}
</body>
{# form.html #}
{% extends "base.html" %}

{% block content %}
    <form id="contact">...</form>

    {% push "scripts" %}
        <script src="/js/validation.js"></script>
    {% end %}

    {% push "inline_js" %}
        <script>
            document.getElementById('contact')
                .addEventListener('submit', validateForm);
        </script>
    {% end %}
{% end %}

Meta Tags

Build up<meta>tags from child templates:

{# base.html #}
<head>
    <meta charset="utf-8">
    {% stack "meta" %}
    <title>{{ page.title }} - My Site</title>
</head>
{# blog-post.html #}
{% extends "base.html" %}

{% block content %}
    {% push "meta" %}
        <meta name="description" content="{{ post.summary }}">
        <meta property="og:title" content="{{ post.title }}">
        <meta property="og:image" content="{{ post.image }}">
    {% end %}

    <article>{{ post.body }}</article>
{% end %}

See Also

  • Inheritance -- Template inheritance with extends and blocks
  • Includes -- Include partial templates
  • Compiler -- How push/stack compiles to Python