HTML/SVG generation for Python. Zero dependencies.
Project description
html-tags
[!WARNING] Under active development — Mar 2026
Concise, immutable HTML/SVG generation for Python. Zero dependencies. Purely functional — data and rendering are fully separated.
from html_tags import setup_tags, setup_svg, to_html
setup_tags(); setup_svg()
page = Html(
Head(Title("hello")),
Body(
H1("hello world", cls="title"),
Svg(Circle(cx="50", cy="50", r="40")),
)
)
print(to_html(page))
Core design
Tags are inert data — Tag is a namedtuple describing structure. Rendering is a function that operates on it:
to_html(tag)— render to HTML stringpretty(tag)— indented HTML for debuggingvalidate(tag)— check structural rulesHTML(tag)— thin wrapper for notebook display
>>> Div(P("hello"), cls="main")
Div(cls='main')(P('hello'))
>>> to_html(Div(P("hello"), cls="main"))
'<div class="main"><p>hello</p></div>'
Immutable construction
Tags are immutable — call to append children or merge attributes:
Div(cls="card")("content", id="main")
SVG elements self-close correctly (<line />) and shapes like Circle accept children for animations:
Circle(cx=50, cy=50, r=20, fill='blue')(
AnimateTransform(attributeName='transform', type='rotate',
values='0 50 50;360 50 50', dur='1s', repeatCount='indefinite'))
Parsing HTML
Convert HTML strings back into Tag trees:
>>> html_to_tag('<div id="main"><p class="greeting">hello</p></div>')
Div(id='main')(P(cls='greeting')('hello'))
Raw HTML
Use Safe to pass pre-escaped HTML strings through without escaping:
Safe('<b>already escaped</b>')
Script and Style tags handle raw content natively — no wrapping needed.
Fragments
Group children without a wrapping element:
Fragment(P("one"), P("two"), P("three"))
Gotchas
Keyword attribute names convert _ to -:
Circle(stroke_width="2") # → stroke-width="2"
Dict keys pass through verbatim — use for special syntax or reserved names:
Div({"data-on:click__once": "@post('/api')"})
FeBlend({"mode": "multiply"})
HTML void elements render without slash (<br>), SVG elements self-close (<line />).
Positional args must come before keyword args (Python rule) — use __call__ chaining to add children after attrs:
Circle(cx=50, cy=50, r=20)(Animate(attributeName='r', values='18;22;18', dur='2s'))
Security
Validates against injection, not HTML structure:
<script>/<style>content checked for closing tag injection- URL attributes reject
javascript:andvbscript:schemes - Attribute names validated against injection patterns
- Void elements reject children,
<html>rejects nesting
Structural correctness (e.g. <li> inside <ul>) is left to the browser.
Install
pip install html-tags
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters