Macros
======
When the same block of HTML appears in several places you can extract it into a
*macro* - a reusable template fragment that you call like a function. This page
shows how to define, call and organize them.
**Key topics**
* Defining a macro with ``macro`` / ``endmacro``.
* Calling a macro and passing arguments.
* Content macros vs attribute macros.
* Practical examples for calendar tables and day styling.
* Where to place macros in your template.
Defining a macro
----------------
A macro starts with ``%% macro name(parameters)`` and ends with ``%% endmacro``.
Everything between them is the macro body.
**Template:**
.. code-block:: html+jinja
%% macro greeting(name)
Hello, {{ name }}!
%% endmacro
{{ greeting("Alice") }}
{{ greeting("Bob") }}
**Output:**
.. code-block:: html
Hello, Alice!
Hello, Bob!
The macro is called with ``{{ greeting("Alice") }}``. The argument ``"Alice"``
is assigned to the parameter ``name`` inside the body.
Multiple parameters
~~~~~~~~~~~~~~~~~~~
Macros can accept more than one parameter:
**Template:**
.. code-block:: html+jinja
%% macro badge(label, color)
{{ label }}
%% endmacro
{{ badge("Weekend", "red") }}
{{ badge("Workday", "black") }}
**Output:**
.. code-block:: html
Weekend
Workday
Content macros vs attribute macros
----------------------------------
A *content macro* returns a chunk of HTML that you insert into the page:
.. code-block:: html+jinja
%% macro month_table(month)
%% for week in month.table
%% for day in week
%% if day is not none
| {{ day }} |
%% else
|
%% endif
%% endfor
%% endfor
%% endmacro
You call it where you want the rows to appear:
.. code-block:: html+jinja
{{ month_table(january) }}
An *attribute macro* returns a small fragment meant to be placed inside an HTML
tag - for example a ``style`` attribute:
.. code-block:: html+jinja
%% macro day_style(day)
{% if day.is_off_day %} style="color: #C00000"{% endif %}
%% endmacro
You call it inside the opening tag with a special ``{{-`` syntax that strips the
whitespace before the output:
.. code-block:: html+jinja
{{ day.short_name }} |
**Output for a weekday:**
.. code-block:: html
Mon |
**Output for Saturday:**
.. code-block:: html
Sat |
The ``-`` after ``{{`` removes the space that would otherwise appear before
``style``. Without it you would get ````.
.. tip::
Use ``{{-`` (with the dash) when inserting a macro result directly into an
HTML tag to avoid extra spaces.
Combining macros
----------------
Macros can call other macros. For example, a ``month_table`` macro can use
``day_style`` to color off-days:
.. code-block:: html+jinja
%% macro day_style(day)
{% if day.is_off_day %} style="color: #C00000"{% endif %}
%% endmacro
%% macro month_table(month)
%% for week in month.table
|
%% for day in week
%% if day is not none
|
{{ day }}
|
%% else
|
%% endif
%% endfor
%% endfor
%% endmacro
If you later want to change how off-days look you only edit ``day_style`` once
and every table that calls it picks up the change.
Where to place macros
---------------------
Define all macros before the ```` line:
**Do:**
.. code-block:: html+jinja
%% macro day_style(day)
{% if day.is_off_day %} style="color: #C00000"{% endif %}
%% endmacro
%% macro month_table(month)
...
%% endmacro
...
**Don't** - define macros in the middle of the HTML:
.. code-block:: html+jinja
%% macro day_style(day)
...
%% endmacro
...
Keeping macros at the top makes them easy to find and ensures they are defined
before they are called.
Do and don't summary
--------------------
.. list-table::
:header-rows: 1
:widths: 50 50
* - Do
- Don't
* - Place macros before ````.
- Scatter macros throughout the body.
* - Use macros to avoid copy-pasting repeated HTML.
- Duplicate the same table code in five places.
* - Use ``{{-`` when inserting into an HTML tag.
- Use ``{{`` inside a tag (leaves extra whitespace).
* - Close every macro with ``%% endmacro``.
- Forget ``endmacro`` - the template will not render.
Update the Demo Planner
-----------------------
Refactor the month calendar from the previous page. Move the off-day styling
and the table rows into macros. Your ``planners/demo/demo.html``
should now look like this:
.. code-block:: html+jinja
%% set year = calendar.year(2026)
%% set month = year.months[0]
%% macro day_color(day)
{% if day.is_off_day %}#C00000{% else %}#000000{% endif %}
%% endmacro
%% macro month_table(month)
%% for week in month.table
%% for day in week
%% if day is not none
| {{ day }} |
%% else
|
%% endif
%% endfor
%% endfor
%% endmacro
## -- Cover page --
Demo Planner - {{ month }} {{ year }}
## -- Month calendar --
{{ month }}
{{ year }}
%% for wd in calendar.weekdays
| {{ wd.short_name }} |
%% endfor
{{ month_table(month) }}
Regenerate::
pyplanner planners/demo
The output should look the same as before, but the template is now shorter and
easier to maintain. The ``day_color`` macro can be reused on day pages later.
What is next
------------
Continue to :doc:`data-reference` for a complete list of every object and
property available in your templates.