# How to Combine Jinja Templates with Air Tags in Python Views

> Easily combine Jinja templates with Air Tags in Python views. Learn how to render Jinja into SafeStr objects for seamless embedding and dynamic content creation.

- Repository: [Feldroy/air](https://github.com/feldroy/air)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Use `JinjaRenderer` with `as_string=True` to render Jinja templates into `SafeStr` objects that can be embedded directly inside Air Tag compositions.**

The `feldroy/air` repository provides a seamless mechanism for mixing traditional Jinja2 templating with its component-based Air Tag system. By leveraging the `as_string` parameter in `JinjaRenderer`, developers can generate HTML from Jinja templates and inject that output into Air Tag hierarchies within the same view function.

## Understanding the JinjaRenderer and SafeStr Mechanism

Air’s templating integration centers on two key components: the `JinjaRenderer` class and the `SafeStr` utility.

The `JinjaRenderer.__call__` method, defined in [`src/air/templating.py`](https://github.com/feldroy/air/blob/main/src/air/templating.py) (lines 118-148), handles template rendering. When invoked with `as_string=True`, the renderer extracts the response body from the Jinja2 template, decodes it to UTF-8, and wraps the result in a `SafeStr` object from `air.tags.utils`.

`SafeStr` is a thin subclass of Python’s built-in `str` that signals to Air’s rendering engine that the content is already safe HTML. This prevents automatic escaping when the string is inserted into parent Air Tags, allowing seamless composition of Jinja-generated markup with programmatically constructed Air Tag trees.

## Implementing Combined Jinja and Air Tag Views

### Basic Implementation with as_string=True

To combine Jinja templates with Air Tags, initialize a `JinjaRenderer` pointing to your templates directory, then call it with `as_string=True` inside your view function:

```python
import air

app = air.Air()
jinja = air.JinjaRenderer("templates")

@app.page
def hybrid_view(request: air.Request) -> air.BaseTag:
    # Render Jinja template to SafeStr

    jinja_output = jinja(
        request, 
        "content.html", 
        as_string=True
    )
    
    # Embed in Air Tag layout

    return air.layouts.mvpcss(
        air.Title("Hybrid Page"),
        jinja_output,  # Jinja content injected here

    )

```

### Complete Working Example

The repository includes a runnable example in [`examples/jinja_in_air_tags.py`](https://github.com/feldroy/air/blob/main/examples/jinja_in_air_tags.py) demonstrating this pattern. The corresponding template at [`examples/jinja_in_air_tags.html`](https://github.com/feldroy/air/blob/main/examples/jinja_in_air_tags.html) contains standard Jinja markup:

```html
<h1>Hello from Jinja</h1>
<p>This content is rendered by Jinja2 but embedded in an Air Tag layout.</p>

```

The view function combines this template with Air’s `mvpcss` layout:

```python
@app.page
def index(request: air.Request) -> air.BaseTag:
    jinja_html = jinja(request, "jinja_in_air_tags.html", as_string=True)
    
    return air.layouts.mvpcss(
        air.Title("Home Page"),
        jinja_html,
    )

```

## Alternative: Returning Raw HTMLResponse

If you do not need to embed Jinja output inside Air Tags, omit the `as_string` parameter. This returns a standard Starlette `HTMLResponse` directly:

```python
@app.page
def plain_jinja(request: air.Request) -> air.Response:
    return jinja(request, "plain.html")  # Returns HTMLResponse

```

This approach is suitable for pages that use Jinja exclusively without Air Tag composition.

## Summary

- **Use `as_string=True`** in `JinjaRenderer` to obtain a `SafeStr` object instead of an `HTMLResponse`.
- **`SafeStr`** marks Jinja-generated HTML as safe for embedding in Air Tags without escaping.
- **Reference implementation** resides in [`src/air/templating.py`](https://github.com/feldroy/air/blob/main/src/air/templating.py), specifically the `JinjaRenderer.__call__` method (lines 118-148).
- **Runnable examples** are available in [`examples/jinja_in_air_tags.py`](https://github.com/feldroy/air/blob/main/examples/jinja_in_air_tags.py) and [`tests/test_templating.py`](https://github.com/feldroy/air/blob/main/tests/test_templating.py) (lines 101-112).

## Frequently Asked Questions

### What is SafeStr in Air?

`SafeStr` is a string subclass defined in `air.tags.utils` that indicates to Air’s rendering engine that the content is already safe HTML and should not be escaped. When `JinjaRenderer` is called with `as_string=True`, it returns a `SafeStr` instance containing the rendered template output, allowing seamless insertion into Air Tag trees.

### Can I use multiple Jinja templates in one Air Tag?

Yes. You can call `JinjaRenderer` multiple times with different template names, each time using `as_string=True` to obtain separate `SafeStr` objects. These can then be passed as multiple children to a parent Air Tag or layout, effectively combining content from several Jinja templates within a single view response.

### Where is JinjaRenderer defined in the Air source code?

`JinjaRenderer` is implemented in [`src/air/templating.py`](https://github.com/feldroy/air/blob/main/src/air/templating.py). The core logic for the `as_string` functionality resides in the `__call__` method between lines 118 and 148, where the method constructs the Jinja context, renders the template, and conditionally wraps the output in `SafeStr` based on the `as_string` parameter.

### How do I test views that combine Jinja and Air Tags?

The test suite in [`tests/test_templating.py`](https://github.com/feldroy/air/blob/main/tests/test_templating.py) (lines 101-112) provides examples of testing this integration. You can instantiate `JinjaRenderer` with a test templates directory, invoke it with `as_string=True`, and assert that the returned object is a `SafeStr` instance that can be composed with Air Tags. For integration testing, use Air’s test client to verify the final rendered HTML output contains both the Jinja-generated content and the Air Tag markup.