Python 3.14’s new t‑strings add flexibility and power to the language’s arsenal of string processing tools. They make it easy to distinguish between static and dynamic content—essential for safe web templating.
That’s why we’re excited to introduce tdom
, a brand-new toolkit that leverages t‑strings and brings Python web templates into the modern era. It’s easy to use: write HTML in a t‑string and pass it to tdom.html()
:
>>> from tdom import html
>>>
>>> name = "Alice"
>>> node = html(t'<div>Hi, {name}!</div>')
>>> str(node)
'<div>Hi, Alice!</div>'
Under the hood, html()
parses your HTML and returns a tree of nodes. These can be manipulated, inspected, or rendered with str()
.
As you’d expect, html()
safely escapes special characters found in substitutions:
>>> from tdom import html
>>>
>>> name = '<script>alert("pwned")</script>'
>>> node = html(t'<div>Hi, {name}!</div>')
>>> str(node)
'<div>Hi, <script>alert("pwned")</script>!</div>'
In addition to keeping things safe, tdom
provides a wide variety of convenience features that make it easier to build complex HTML. If you’ve ever used lit-html
, htm
, JSX, or similar JavaScript tools, you’ll feel right at home.
To give a taste of what’s possible, here are a few highlights:
-
Dynamic attributes: Provide individual attributes or many at once with a dict. Boolean attributes are handled the way you’d expect.
>>> from tdom import html >>> >>> attrs = {"id": "yum", "data-act": "submit"} >>> disabled = True >>> t = t'<button {attrs} disabled={disabled}>Yum!</button>' >>> node = html(t) >>> str(node) '<button id="yum" data-act="submit" disabled>Yum!</button>'
-
Special handling for special attributes: Deftly deal with
data-*
,aria-*
,style
andclass
attributes. Inspired byclassnames
, CSS classes can be provided as strings, lists, or dicts.>>> from tdom import html >>> >>> classes = ["btn", {"primary": False}, {"active": True}] >>> node = html(t'<button class={classes}>Click me</button>') >>> str(node) '<button class="btn active">Click me</button>'
-
Intuitive content: Pass strings, nodes, iterables, and much more as child content;
tdom
will flatten and render it all correctly.>>> from tdom import html >>> >>> cheeses = ["Cheddar", "Gouda", "Brie"] >>> items = (t'<li>{cheese}</li>' for cheese in cheeses) >>> node = html(t'<ul>{items}</ul>') >>> str(node) '<ul><li>Cheddar</li><li>Gouda</li><li>Brie</li></ul>'
-
Components: Define reusable functions and classes and invoke them directly in your templates using a JSX-like syntax.
>>> from tdom import html, Node >>> >>> def Card( ... children: Node, ... title: str, ... subtitle: str | None = None, ... **attrs, ... ) -> Node: ... return html(t''' ... <div class="card" {attrs}> ... <h2>{title}</h2> ... {subtitle and t'<h3>{subtitle}</h3>'} ... <div class="content"> ... {children} ... </div> ... </div>''') >>> >>> result = html(t''' ... <{Card} title="Cheese Emporium" id="cheeses"> ... <p>Sorry, we're fresh out!</p> ... </{Card}>''') >>> >>> str(result) <div class="card" id="cheeses"> <h2>Cheese Emporium</h2> <div class="content"> <p>Sorry, we're fresh out!</p> </div> </div>
-
Much more. If it feels intuitive, it’s probably supported… or should be! See the README for a rundown of current features.
Please give tdom
a try and send us your feedback! If you have Astral’s uv
installed, you can use tdom
today with:
uv run --with tdom --python 3.14 python
The tdom
project is still very young; it’s labeled pre-alpha on PyPI and there’s still plenty of work for us to do. If you run into bugs, please open an issue; if there’s an intuitive feature you’d like to see that we don’t yet support, please start a discussion and we’ll find a way to make it happen.
tdom
is part of a broader ecosystem of tools designed to promote modern HTML templating in Python. Early efforts are underway to add syntax highlighting, formatting, linting, and autocompletion support to popular editors like VS Code and PyCharm. Stay tuned for updates!