Macros & Includes

Difficulty: Beginner

Macros and includes are two mechanisms for reusing template code in Jinja2. While template inheritance defines the overall page structure, macros and includes let you create reusable components and partial templates that you can use across multiple pages.

The {% include %} tag inserts the content of another template at that point. It is the simplest way to share template fragments. Common use cases include header, footer, and sidebar partials. The included template has access to all variables from the current context. You can pass additional variables with the 'with context' clause, though this is the default behavior in Flask.

You can use 'ignore missing' with include to prevent errors when a template might not exist: {% include 'sidebar.html' ignore missing %}. You can also provide a list of template names and Jinja2 will use the first one that exists: {% include ['custom.html', 'default.html'] %}.

Macros are like functions in templates. They accept arguments, have a body, and return rendered content. You define them with {% macro name(args) %}...{% endmacro %}. Macros are called like functions: {{ macros.render_field(form.username) }}. They are the Jinja2 equivalent of React components.

Macros are typically defined in a separate file and imported. The {% from 'macros.html' import render_field %} syntax imports specific macros. The {% import 'macros.html' as macros %} syntax imports the entire file as a namespace. Imported macros do not have access to the current template's variables by default. Add 'with context' if they need it.

Macros support default argument values, keyword arguments, and even variable arguments through the special 'varargs' and 'kwargs' variables. Inside a macro, the 'caller' variable gives access to the content between the call block's tags, enabling a pattern similar to slots or children in component frameworks.

The combination of inheritance, includes, and macros gives you a complete component system. Inheritance handles page structure, includes handle simple partials, and macros handle reusable components with parameters. This layered approach keeps templates organized and DRY.

A common pattern is to create a forms macro file that defines macros for rendering form fields, error messages, and buttons. This lets you change the appearance of all forms in your application by editing a single file.

Code examples

Using include for Partials

<!-- templates/base.html -->
<html>
<body>
    {% include 'partials/nav.html' %}

    <main>{% block content %}{% endblock %}</main>

    {% include 'partials/footer.html' %}
</body>
</html>

<!-- templates/partials/nav.html -->
<nav>
    <a href="{{ url_for('index') }}">Home</a>
    <a href="{{ url_for('about') }}">About</a>
    {% if current_user.is_authenticated %}
        <a href="{{ url_for('logout') }}">Logout</a>
    {% endif %}
</nav>

<!-- templates/partials/footer.html -->
<footer>
    <p>&copy; {{ current_year }} My App</p>
</footer>

{% include %} inserts another template's content inline. Included templates have access to all current context variables. This is ideal for partials like navigation, headers, and footers.

Defining and Using Macros

<!-- templates/macros/forms.html -->
{% macro render_field(field) %}
    <div class="form-group">
        <label for="{{ field.id }}">{{ field.label.text }}</label>
        {{ field(class='form-control') }}
        {% if field.errors %}
            <div class="errors">
            {% for error in field.errors %}
                <span class="text-danger">{{ error }}</span>
            {% endfor %}
            </div>
        {% endif %}
    </div>
{% endmacro %}

{% macro render_button(text, type='submit', class='btn-primary') %}
    <button type="{{ type }}" class="btn {{ class }}">{{ text }}</button>
{% endmacro %}

Macros are defined with {% macro name(args) %}. They accept parameters with optional defaults. This forms macro renders WTForms fields with labels, error messages, and consistent styling.

Importing Macros

<!-- Import specific macros -->
{% from 'macros/forms.html' import render_field, render_button %}

<form method="POST">
    {{ form.hidden_tag() }}
    {{ render_field(form.username) }}
    {{ render_field(form.email) }}
    {{ render_field(form.password) }}
    {{ render_button('Sign Up') }}
</form>

<!-- Or import as namespace -->
{% import 'macros/ui.html' as ui %}

{{ ui.card('Title', 'Card content here') }}
{{ ui.alert('Success!', type='success') }}
{{ ui.badge('New', color='green') }}

Use 'from ... import' for specific macros or 'import ... as' for a namespace. Macros are called like functions with parentheses. This keeps your templates clean and component-like.

Macro with Caller Block

<!-- templates/macros/ui.html -->
{% macro card(title, footer=None) %}
    <div class="card">
        <div class="card-header">
            <h3>{{ title }}</h3>
        </div>
        <div class="card-body">
            {{ caller() }}
        </div>
        {% if footer %}
        <div class="card-footer">{{ footer }}</div>
        {% endif %}
    </div>
{% endmacro %}

<!-- Usage with call block -->
{% from 'macros/ui.html' import card %}

{% call card('User Profile') %}
    <p>Name: {{ user.name }}</p>
    <p>Email: {{ user.email }}</p>
    <img src="{{ user.avatar }}" alt="Avatar">
{% endcall %}

The call/caller pattern lets you pass a block of content to a macro, similar to React children or Vue slots. The macro calls {{ caller() }} to render the content passed inside the {% call %} block.

Key points

Concepts covered

Macros, include, import, Reusable Components, Partial Templates