Why partials
It is Friday afternoon. The designer drops by with good news: the video card on your site is getting a small refresh. A rounded thumbnail, a view count with thousands separators, the author’s name as a link. Fifteen minutes of work, they say. You open the templates folder and start counting. The home page has a card. The listing page has a card. The search results have a card. The “related videos” sidebar has a smaller card that is really the same card with a class on it. The email digest has a card too, and that one was copied from the listing page sometime last spring and never updated. You stop counting at fourteen.
Every one of those cards started as a copy and paste, because Jinja templates made copying easy and reuse awkward. In Python you would never tolerate fourteen copies of a function. You would write it once, give it parameters, and call it. jinja_partials lets templates do exactly that:
{% for v in videos %}
{{ render_partial('shared/partials/video_square.html', video=v) }}
{% endfor %}render_partial takes the path of a fragment file and the data it needs, renders it, and hands back safe HTML. The fragment sees video and nothing else. It cannot quietly depend on some page-level variable, because it never receives any. You can read its inputs at the call site, rename at the boundary (the loop’s v becomes the card’s video), and nest fragments inside fragments as deep as the design needs. The designer’s fifteen minutes become fifteen minutes.
But Jinja already has include and macro
It does, and both are nearly right, which is exactly why they are frustrating.
{% include %} is a copy and paste that happens at render time. The fragment receives the entire context of whoever included it: every variable on the page, whether the fragment needs it or not. Nothing at the call site tells you what the fragment reads. Rename a variable on one page and the fragment breaks on that page only, and the fix is a {% with %} wrapper around the include, which is a parameter list wearing a disguise.
{% macro %} gets the parameters right. The trouble is where macros live. A macro is defined inside a template file, so every page that wants the card has to {% import %} that file first. And a macro is not itself a template you can render. When HTMX asks your server for just one card to swap into the page, you cannot hand it a macro. With partials, the same video_square.html renders inside a page and also serves as a fragment response straight from a route, because it was a plain template all along.
The original back-and-forth on this question is preserved in issue #1, if you enjoy that sort of thing.
What is in the box
- One registration call per app, at startup. Flask, Quart, FastAPI, Starlette, or any Jinja2 environment you have lying around.
- A
render_partial(template_name, **data)global inside every template. It returnsMarkup, so autoescaping leaves your fragment alone. - The same function callable from Python, which makes a partial the easiest thing in your app to unit test.
- Async apps are welcome. Quart, FastAPI, and Starlette with
enable_async=Trueenvironments render partials on a small thread pool tied to the app’s lifespan, and your templates do not change at all. The mechanics are in How it works, and they are more interesting than they sound.
Two minutes to install, then the Quickstart builds that video card properly, once.