For more than a decade, Django's template system has been a model of stability and reliability. Its block and inheritance approach is simple, solid, and effective for many projects. Yet, when building user interfaces in a component-oriented style, its limitations become apparent. Another built-in backend, Jinja, extends these boundaries with macros and allows for more flexible templates. Yet, some projects demand even greater composability.
Leading front-end frameworks like React with JSX achieve this through two foundational concepts: props and slots. Props allow data to flow into components, while slots define flexible content areas. Together, these mechanisms have become the de facto standard for creating highly modular, maintainable, and reusable, user interfaces.
The design philosophy behind Mako, naturally
supports component-oriented development. Its <%def> syntax enables template
fragments to receive parameters like props and define flexible content areas
similar to slots.
Additionally, Mako templates run anywhere in Python, can be reused across projects with different stacks, and they deliver high performance.
Consider defining a set of reusable button components with clean, composable
logic. The base_button function encapsulates shared behavior (dynamic class
generation, optional icon, and label handling), while variants like
basic_button and icon_button extend it without duplicating code.
For convenience, tools like clsx-py help manage class names dynamically.
button.html.mako:
<%def
name="base_button(
class_name=None,
icon=None,
label=None,
round=False,
rounded=False,
)"
>
<button
class="${ clsx([
'button',
('button--round', round),
('button--rounded', rounded),
class_name,
]) }"
>
% if icon:
<span class="button__icon">
${ icon() }
</span>
% endif
% if label:
<span class="button__label">
${ label() }
</span>
% endif
</button>
</%def>
<%def name="basic_button(class_name=None, rounded=False)">
<%self:base_button
class_name="${ ['button--basic', class_name] }"
icon="${ getattr(caller, 'icon', None) }"
label="${ getattr(caller, 'label', None) }"
rounded="${ rounded }"
/>
</%def>
<%def name="icon_button(class_name=None, round=False)">
<%self:base_button
class_name="${ ['button--icon', class_name] }"
icon="${ getattr(caller, 'body', None) }"
round="${ round }"
/>
</%def>Import the namespace from button.html.mako and build HTML declaratively.
page.html.mako:
<%namespace name="button" file="button.html.mako" />
<div>
<%button:icon_button class_name="sample-button">
➖
</%button:icon_button>
</div>
<div>
<%button:icon_button class_name="sample-button" round="${ True }">
➕
</%button:icon_button>
</div>
<div>
<%button:basic_button class_name="sample-button">
<%def name="icon()">✖️</%def>
<%def name="label()">Cancel</%def>
</%button:basic_button>
</div>
<div>
<%button:basic_button class_name="sample-button" rounded="${ True }">
<%def name="icon()">⚡</%def>
<%def name="label()">Trigger</%def>
</%button:basic_button>
</div>Output:
<div>
<button class="button button--icon sample-button">
<span class="button__icon">➖</span>
</button>
</div>
<div>
<button class="button button--round button--icon sample-button">
<span class="button__icon">➕</span>
</button>
</div>
<div>
<button class="button button--basic sample-button">
<span class="button__icon">✖️</span>
<span class="button__label">Cancel</span>
</button>
</div>
<div>
<button class="button button--rounded button--basic sample-button">
<span class="button__icon">⚡</span>
<span class="button__label">Trigger</span>
</button>
</div>Years ago, I built a custom Django backend for Mako to bring this JSX-like developer experience into a closed-source project. The components scaled naturally, and UI code stayed organized.
Recently, I rebuilt that integration from scratch, modernized it, and released it as open source.
With just a few lines in settings.py, Mako becomes part of your Django
project, ready to render templates like the examples above. Templates in mako
directories within apps are auto-discovered, context processors work out of the
box, and any errors are displayed in detail through Django's debug page.
- Install the library:
pip install mako-for-djangoNote
Examples above use the class name utility:
pip install clsx- Enable the template backend in
settings.py:
TEMPLATES = [
{
"BACKEND": "django_mako.MakoEngine",
"DIRS": [
BASE_DIR / "mako",
],
"APP_DIRS": True,
"OPTIONS": {},
},
]- Render templates using Django's own APIs:
from clsx import clsx
from django.shortcuts import render
def page(request):
return render(request, "page.html.mako", {"clsx": clsx})That's all it takes. Mako for Django respects Django's conventions, and
delivers a modern component-driven templating experience.
Check out the GitHub repository for the source code, documentation, and additional examples, including some screenshots.