# How to Create Custom Air Tags for HTML Rendering: A Complete Guide

> Learn to create custom Air Tags for HTML rendering by subclassing BaseTag or Transparent from feldroy/air. Override init to set defaults and register automatically for custom HTML elements.

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

---

**Create custom Air Tags by subclassing `BaseTag`, `Transparent`, or `UnSafeTag` from the `feldroy/air` repository, overriding `__init__` to set defaults, and letting the automatic `__init_subclass__` registration handle the rest.**

Creating custom Air Tags for HTML rendering allows you to extend the Air framework with reusable, type-safe components that integrate seamlessly with Python's syntax. This guide covers the core architecture of the `feldroy/air` repository and provides practical examples for building everything from simple styled buttons to transparent layout containers.

## Core Architecture of Air Tags

### BaseTag and Automatic Registration

In [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py), the `BaseTag` class serves as the foundation for all HTML elements. The `__init_subclass__` method automatically registers every new subclass in a global registry when the class is defined, making it available for reconstruction via `air.Tag.from_html` without manual configuration.

### Specialized Base Classes

Located in [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py), these bases provide specific rendering behaviors for different use cases:

- **Transparent**: Renders only children without producing a wrapper element.
- **UnSafeTag**: Bypasses HTML escaping for raw content injection.
- **Tag/Tags/Children/Fragment**: Convenient aliases for `Transparent`.

## Creating a Simple Custom Air Tag

### Subclassing Existing Tags

The most straightforward way to create custom Air Tags for HTML rendering is to subclass an existing tag like `air.Button` or `air.Div`. This preserves correct void/pair element behavior and attribute handling while allowing you to inject default values.

```python
import air

class MyButton(air.Button):
    """A button with default CSS classes and optional type attribute."""
    
    def __init__(self, *children: air.Renderable, *, type_: str = "button", **attrs: air.AttributeType):
        default_attrs = {"class_": "my-button", "type_": type_}
        super().__init__(*children, **default_attrs | attrs)

```

### Usage in Application Code

```python
@app.page
def home():
    return air.Html(
        air.Body(
            air.H1("Welcome"),
            MyButton("Click Me", id_="cta"),
        )
    )

```

This renders:

```html
<button class="my-button" type="button" id="cta">Click Me</button>

```

## Creating Transparent Container Tags

For layout helpers that group children without generating a wrapper element, subclass `Transparent` from [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py). This keeps the DOM clean while allowing you to enforce structure.

```python
class Card(air.Transparent):
    """A card component that wraps content in a styled DIV."""
    
    def __init__(self, *children: air.Renderable, **attrs: air.AttributeType):
        container = air.Div(*children, class_="card", **attrs)
        super().__init__(container)

```

The `Card` class itself never appears in the final HTML—only the `div class="card"` wrapper does.

## Bypassing HTML Escaping with UnSafeTag

To inject raw HTML without escaping, create a custom tag inheriting from `UnSafeTag` in [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py).

```python
class RawSvg(air.UnSafeTag):
    """Embeds SVG content without HTML escaping."""
    
    def __init__(self, svg_content: str):
        super().__init__(svg_content)

```

Usage:

```python
svg = """<svg width="100" height="100"><circle cx="50" cy="50" r="40"/></svg>"""
page = air.Html(air.Body(RawSvg(svg)))

```

**Warning:** `UnSafeTag` performs no sanitization. Use only with trusted content to avoid XSS vulnerabilities.

## Complete Example: Building a Reusable Card Component

Here is a comprehensive example combining multiple techniques:

```python
import air

class Card(air.Transparent):
    """A reusable card with title and body sections."""
    
    def __init__(
        self,
        title: str,
        *body: air.Renderable,
        **attrs: air.AttributeType,
    ):
        header = air.H2(title, class_="card-title")
        content = air.Div(*body, class_="card-body")
        wrapper = air.Div(header, content, class_="card", **attrs)
        super().__init__(wrapper)

# Application usage

@app.page
def dashboard():
    return air.Html(
        air.Body(
            Card(
                "Statistics",
                air.P("Visitors: 1234"),
                air.P("Sign-ups: 56"),
            ),
            Card(
                "About",
                air.P("This app demonstrates custom Air tags."),
                class_="highlight",
            ),
        )
    )

```

This generates semantic HTML with proper nesting and CSS classes while maintaining clean Python syntax.

## Key Source Files for Custom Tag Development

When creating custom Air Tags for HTML rendering, reference these files in the `feldroy/air` repository:

| File | Purpose |
|------|---------|
| [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py) | Contains `BaseTag` class and `__init_subclass__` registration logic. |
| [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py) | Defines `Transparent`, `UnSafeTag`, and container aliases. |
| [`src/air/tags/__init__.py`](https://github.com/feldroy/air/blob/main/src/air/tags/__init__.py) | Re-exports all built-in tags for easy importing. |
| [`examples/tags_render.py`](https://github.com/feldroy/air/blob/main/examples/tags_render.py) | Reference implementation showing rendering patterns for built-in tags. |

## Best Practices for Custom Air Tags

- **Inherit from concrete tags** when possible (e.g., `air.Button` instead of `BaseTag`) to preserve correct void/pair element behavior and attribute handling.

- **Use `locals_cleanup`** when overriding `__init__` to prevent internal variables from leaking into attribute dictionaries. Most built-in tags use `locals_cleanup(locals())` to strip helper variables before passing to `super().__init__`.

- **Prefer `Transparent` for layouts** when you need grouping logic without adding DOM noise, keeping the output HTML clean.

- **Reserve `UnSafeTag` for trusted content** only—never use it with user input due to the lack of HTML escaping and potential XSS vulnerabilities.

- **Document your defaults** in docstrings to maintain IDE autocomplete support and clarify the component interface for other developers.

## Summary

- **Subclass `BaseTag`** or specialized bases like `air.Button` to create custom Air Tags for HTML rendering with automatic registration via `__init_subclass__` in [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py).
- **Use `Transparent`** from [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py) for container components that render only their children without generating wrapper elements.
- **Leverage `UnSafeTag`** when you need to inject raw HTML without escaping, but only with trusted content to avoid security risks.
- **Reference key files**: [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py) for core logic, [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py) for specialized behaviors, and [`examples/tags_render.py`](https://github.com/feldroy/air/blob/main/examples/tags_render.py) for rendering patterns.
- **Follow best practices**: inherit from concrete tags, use `locals_cleanup` to prevent attribute leakage, and document your component interfaces.

## Frequently Asked Questions

### How do I register a custom Air Tag so it can be reconstructed from HTML?

Air Tags are automatically registered when you define the class thanks to the `__init_subclass__` method in [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py). Simply subclass `BaseTag` or any existing tag like `air.Button`, and the framework handles registration immediately. This enables reconstruction via `air.Tag.from_html` without requiring manual registry configuration.

### What is the difference between `BaseTag` and `Transparent` when creating custom tags?

`BaseTag` in [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py) is the foundation for all HTML elements and renders a proper HTML tag with attributes and children. `Transparent` in [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py) is a specialized base that renders only its children without producing any wrapper element, making it ideal for layout helpers that need to group content without adding DOM noise.

### How can I prevent HTML escaping in my custom Air Tag?

To bypass HTML escaping, inherit from `UnSafeTag` defined in [`src/air/tags/models/special.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/special.py). This base class skips the normal escaping logic used by `BaseTag`, allowing raw HTML strings to be emitted verbatim. Use this only with trusted content, as `UnSafeTag` performs no sanitization and can introduce XSS vulnerabilities if used with user input.

### Where should I place my custom Air Tag definitions in my project?

Place custom tag definitions in a dedicated module within your project, such as [`myapp/tags.py`](https://github.com/feldroy/air/blob/main/myapp/tags.py) or [`components/ui.py`](https://github.com/feldroy/air/blob/main/components/ui.py). Import them alongside the `air` module using `import air; from myapp.tags import MyButton`. Since registration happens automatically via `__init_subclass__` in [`src/air/tags/models/base.py`](https://github.com/feldroy/air/blob/main/src/air/tags/models/base.py), no additional configuration is required as long as the module containing your custom tag is imported during application startup.