A lightweight, type-safe Python library for building and parsing HTML documents programmatically without templates.
📚 Full documentation: https://mosquito.github.io/tagz/
tagz lets you construct HTML using pure Python code with a clean, intuitive API. No template engines, no DSLs — just Python functions and objects that map directly to HTML elements.
Key Features:
- Programmatic HTML construction — build documents using Python objects and methods
- HTML parser — parse existing HTML strings back into manipulable
Tagobjects - Type-safe — full mypy support with comprehensive type annotations
- Streaming support — memory-efficient rendering with
iter_lines(),iter_chunk(), anditer_string() - Automatic escaping — XSS protection enabled by default
- CSS helpers — built-in
StyleandStyleSheetobjects - Fragments and raw — group elements without wrapper tags, or splice in pre-rendered HTML
- Page objects — high-level API for complete HTML documents with DOCTYPE support
pip install tagzor with uv:
uv add tagzRequires Python 3.10+.
from tagz import Page, StyleSheet, Style, html
page = Page(
lang="en",
body_element=html.body(
html.h1("Hello"),
html.div(
html.strong("world"),
),
html.a(
"example link",
html.i("with italic text"),
href="https://example.com/"
),
),
head_elements=(
html.meta(charset="utf-8"),
html.meta(name="viewport", content="width=device-width, initial-scale=1"),
html.title("tagz example page"),
html.link(href="/static/css/bootstrap.min.css", rel="stylesheet"),
html.script(src="/static/js/bootstrap.bundle.min.js"),
html.style(
StyleSheet({
"body": Style(padding="0", margin="0"),
(".container", ".container-fluid"): Style(transition="opacity 600ms ease-in"),
})
)
),
)
# `pretty=False` is the fast path; pretty=True produces human-readable output.
output = page.to_html5(pretty=True)
assert output.startswith("<!doctype html>")
assert "<strong>" in outputThe pretty-printed output looks like:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>
tagz example page
</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet"/>
<script src="/static/js/bootstrap.bundle.min.js">
</script>
<style>
body {margin: 0; padding: 0;}
.container, .container-fluid {transition: opacity 600ms ease-in;}
</style>
</head>
<body>
<h1>
Hello
</h1>
<div>
<strong>
world
</strong>
</div>
<a href="https://example.com/">
example link
<i>
with italic text
</i>
</a>
</body>
</html>The full documentation is organised in the Diátaxis style:
- Tutorials — guided lessons that take you from zero to a working page, parser, or streamed document.
- How-to guides — recipe-style answers to specific problems (streaming, callables, async data,
data:URIs, CSV → table, …). - Reference — the full API surface with type signatures.
- Explanation — design rationale: escaping model, callables and laziness, why there's no async render path.
| Feature | Read more |
|---|---|
| Callable children & attributes | How-to: lazy children |
Conditional attributes via ABSENT |
How-to: conditional attributes |
Boolean attributes (checked, disabled, …) |
How-to: boolean attributes |
| Custom / non-standard tag names | How-to: custom tags |
Fragments and unescaped Raw content |
How-to: fragments vs raw |
Inline style= and <style> blocks |
How-to: inline & embedded CSS |
| Streaming to file / socket / ASGI | How-to: stream to a socket |
data: URIs for inline binary data |
How-to: embed binary data |
| Pre-resolving async data | How-to: prefetch async data |
| Serving HTML fragments to htmx (aiohttp) | How-to: htmx + aiohttp — full demo in examples/htmx-asyncio |
| Parsing existing HTML | Tutorial: parse and modify |
MIT — see LICENSE.