Skip to main content

jinja2-component-macros

A package to bring component-oriented HTML to Jinja templates, powered by Jinja macros.

PyPI version Python versions License

Overview

This package provides a Jinja extension that preprocesses Jinja templates, replacing html 'component' tags with corresponding macro calls. Tags eligible for replacement are selected based on the "import" statements at the start of the template.

I like this approach because HTML tags are easier to read than 'macro' and 'endmacro' statements, and work well with IDEs even without Jinja-specific support.

With traditional Jinja macros

{% from "components/cards.html" import Card, CardHeader %}
{% from "components/buttons.html" import Button %}

{% call Card(class="bg-blue") %}
  {% call CardHeader() %}Welcome to my card{% endcall %}
  {{ Button(text="Click me", url=ctx_url, variant="primary") }}
{% endcall %}

Using jinja2-component-macros

{% from "components/cards.html" import Card, CardHeader %}
{% from "components/buttons.html" import Button %}

<Card class="bg-blue">
  <CardHeader>Welcome to my card</CardHeader>
  <Button text="Click me" url='ctx_url' variant="primary" />
</Card>

Installation

pip install jinja2-component-macros

Usage

Basic Setup

from jinja2 import Environment
from jinja2_component_macros import ComponentsExtension

env = Environment(extensions=[ComponentsExtension])

Creating Components

Create your components as regular Jinja macros, with whatever parameters you'd like to use. A very simple example of a self-closing component:

{# components.html #}
{% macro Header(text, class="") %}<h1 class="{{ class }}" {{ kwargs|jcm_attr }}>{{ text }}</h1>{% endmacro %}

Note the above does not use 'caller()', so it will only work as a self-closing component, like <Header text="First Header"/>. A second version that also allows <Header>First Header</Header> might look like:

{# components.html #}
{% macro Header(text="", class="") %}<h1 class="{{ class }}" {{ kwargs|jcm_attr }}>{{ caller() if caller else text }}</h1>{% endmacro %}

These examples also use a filter called jcm_attr, which is a specialized version of xmlattr that can expand the kwargs parameters, but with some extra support for special characters that aren't valid in macro parameter names, for example AlpineJS "@click" and "x-on:load". Meaning you can do things like: <Header @click="open-page=true">First Header</Header> and the '@click' attribute will be passed through to the component properly. The way this is handled "under the hood" is that any special character parameters are passed as a dict to the '' parameter. When jcm_attr expands the kwargs, it looks for a "" key and expands the value of that as additional kwargs parameters.

Using Components

jinja2-component-macros only replaces HTML tags that are listed in import statements at the top of a template.

Import your components first, then use them in HTML:

{% from "components.html" import Button, Card %}

{# Self-closing components #}
<Button text="Save" variant="primary" />

{# Container components #}
<Card title="User Profile" class="bg-light">
  <p>User information goes here</p>
  <OtherComponent>This does not get replaced by a macro call because OtherComponent wasn't imported</OtherComponent>
  <Button text="Edit Profile" variant="secondary" />
</Card>

How It Works

Under the hood, the extension preprocesses the templates, makes a mapping of macro names to convert based on import statements, then scans and converts any matching HTML tags into the appropriate Jinja macro calls:

  • Self-closing tags (<Component />) become {{ ComponentName() }}
  • Container tags (<Component>...</Component>) become {% call ComponentName() %}...{% endcall %}
  • Attributes are passed as macro parameters
  • Attributes with invalid characters (containing -, :, @, .) are collected as a dictionary and passed as a special _ parameter

Attribute Handling

Attributes are parsed in different ways depending on the type of quotes used - this is distinct from how HTML works, so it should be paid special attention.

  • Double quotes ("value") are passed as string literal values to macro parameters
  • Single quotes or unquoted ('value' or value) are treated as Jinja Expressions, and are passed unquoted to macro parameters

As an example:

{# Double quoted attributes... #}
<Button text="Click me" class="btn-primary" is_active="false" />
{# ...become... #}
{{ Button(text="Click me", class="btn-primary", is_active="false") }}

{# Single quoted or unquoted attributes... #}
{% set button_text="some text" %}
<Button text='button_text' is_active=false is_valid='false' count=42 />
{# ...become... #}
{{ Button(text=button_text, is_active=false, is_valid=false, count=42) }}

Especially note the different behaviour of "false" versus false or 'false'. The former is passed as a string, the latter as the actual boolean value for false.

Invalid Parameter Names

Attributes with invalid Python parameter names are collected in a special _ parameter:

<Button x-on:load=123 @click="handleClick" />
{# ...become... #}
{{ Button(_={"x-on:load": 123, "@click": "handleClick"}) }}

Note the same rules for quoting apply, so the unquoted 123 is treated as an expression (an integer), not as a string.

Access these in your macro using the jcm_attr helper filter:

{% macro Button(text) %}
<button{{ kwargs|jcm_attr(autospace=true) }}>{{ text }}</button>
{% endmacro %}

Helper Functions

The extension provides two helpful global functions:

jcm_attr(autospace=False)

Converts a dictionary to HTML attribute string:

{% macro Button() %}
<button {{ kwargs|jcm_attr }}>Click me</button>
{% endmacro %}

{# Usage: <Button data-id="123" class="btn" /> #}
{# Output: <button data-id="123" class="btn">Click me</button> #}

As with xmlattr, you can use autospace=True to make it add a space only when it returns text - if not, then it returns an empty string.

{% macro Button() %}
<button{{ kwargs|jcm_attr(autospace=True) }}>Click me</button>
{% endmacro %}

{# Usage: <Button /> #}
{# Output: <button>Click me</button> #}

classx(*args)

Conditionally joins CSS classes (similar to JavaScript's clsx):

{% set button_classes = classx(
  "btn",
  {"btn-primary": variant == "primary"},
  {"btn-large": size == "lg"},
  extra_classes
) %}
<button class="{{ button_classes }}">{{ text }}</button>

Known Issues

There are plenty of things to improve on this, but a couple of significant potential gotchas:

  • There is currently no handling to ignore comment blocks e.g. {# #} - HTML tag substitution will be applied even inside a comment.

Performance

An area to improve is benchmarking and performance, to look at performance comparisons between this, native macro usage, and other methods of components. However, because this package does almost all its work in pre-processing, it is expected that it should perform almost the same as using macros directly.

Development

This project uses uv for dependency management and just for task running.

# Setup development environment
just bootstrap

# Install dependencies
just install

# Run tests using tox
just test

# Run (roughly) the same checks that are run when checking a PR
just pr-checks

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Metadata

Release files for jinja2-component-macros 2025.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jinja2-component-macros 2025.1
File Size Uploaded Provenance Actions
jinja2_component_macros-2025.1.tar.gz 11.9 kB With provenance Details for jinja2_component_macros-2025.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for jinja2-component-macros 2025.1
File Interpreter ABI Platform Actions
jinja2_component_macros-2025.1-py3-none-any.whl Python 3 none any Details for jinja2_component_macros-2025.1-py3-none-any.whl

Total release size: 21.0 kB

Release files / jinja2_component_macros-2025.1.tar.gz

Download URL jinja2_component_macros-2025.1.tar.gz
Size 11.9 kB
Tags Source
SHA-256 checksum
How to use checksums
74741e670c197e1323be54172ff57c701b119b6e1ef6086b81f8be4ffc3fd6c7
BLAKE2b-256 checksum
How to use checksums
c6cf50cd618cd01f6470aa5161a919f8b9916a7f0cb5ec34cdeb5acdb77e5137
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2025.

Transparency log

Release files / jinja2_component_macros-2025.1-py3-none-any.whl

Download URL jinja2_component_macros-2025.1-py3-none-any.whl
Size 9.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
df0032cf49d4912c23cff32d46e702c9c55ea7ca1877d644b0f32b53c37d4f84
BLAKE2b-256 checksum
How to use checksums
e81ee8b2e2045e8ee36a163c23a706e63632dcf47b482a27b829d171a7b26910
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2025.

Transparency log

Security for jinja2-component-macros 2025.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for jinja2_component_macros-2025.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2025.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2025.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page