# tdom: HTML templating built on Python 3.14 t-strings, with attributes that merge

> A small templating library that turns a t-prefixed template into escaped HTML, and gives special treatment to the class, style, data and aria attributes so dictionaries can be merged and booleans behave. Its own readme is a test target, and the project declares itself pre-alpha with a documentation site it labels incomplete.

**t-strings/tdom** — A 🤘 rockin' t-string HTML templating system for Python 3.14.

- Repository: https://github.com/t-strings/tdom
- Website: https://t-strings.github.io/tdom/
- Stars: 144 · Forks: 6
- Language: Python
- License: MIT
- Published: 2026-08-22 · Updated: 2026-08-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/t-strings-tdom

## Pre-alpha is stated three times before the first example

The status of this project is unusually clearly signposted, and it is worth reading the top of the file before anything else. The first line of the description calls it a pre-alpha package, and the readme has a section headed as a warning that repeats the point in full sentences: it is pre-alpha software, still under heavy development, and the application programming interface may change without warning. The same section asks for community feedback to help shape the direction of the project. Then there is the documentation link, which is labelled as upcoming and incomplete in the link text itself. The manifest's own classifier agrees, marking the development status as pre-alpha, so nothing in the file is trying to talk you past the warning.

## Python 3.14 is a hard floor, and the next version is claimed too

The version requirement is not a suggestion. The manifest declares a floor of 3.14, and the readme says you need 3.14, noting that it was released in October 2025. The install is one command:

```bash
pip install tdom
```

If you already use the uv tool, there is a second route that does not touch your environment at all: a single run command that requests the 3.14 interpreter and the package together, so you can try it and throw it away. The interesting detail is in the classifier rather than the readme. Alongside the floor, the manifest claims support for 3.15 as well, and the test recipe in the repository's task file exercises three interpreter versions, a pinned patch release of 3.14, the 3.14 line generally, and 3.15. So the floor is real and the forward claim is being tested rather than merely stated.

## A t-prefixed template instead of a string, then one function to render it

The mechanism is Python's own. The library is built on the template string feature added in 3.14, and the readme describes how that feature behaves by comparison: t-strings work like f-strings but use a different prefix, and instead of producing a string they produce a template object. Everything this package does sits on top of that one distinction. You build a template, and then you call the library's render function to turn it into a string, and the readme says the resulting feel will be familiar if you have used a JSX-like syntax or one of the two common server-side template languages. The first example is four lines long, an import, a template, and an assertion that the rendered output equals the input markup, which is the shape of every example that follows.

## Interpolation is escaped for you, and the readme proves it by asserting

The security story is a single sentence with a demonstration attached. The render function escapes interpolated values automatically, and the reason given is to prevent cross-site scripting. The example interpolates a value that is itself a script tag, and asserts that the output contains escaped angle brackets and an escaped quote character rather than the raw markup:

```python
user_name = "<script>alert('owned')</script>"
safe_output = html(t"<p>Hello, {user_name}!</p>")
assert (
    safe_output == "<p>Hello, &lt;script&gt;alert(&#39;owned&#39;)&lt;/script&gt;!</p>"
)
```

The escaping library that does the work is also the package's only runtime dependency, pinned to a minimum version. What makes this example more than documentation is covered in its own section below: the readme is collected by the test runner, so if the escaping ever regressed, that assertion would fail the suite rather than quietly mislead someone.

## Bare attribute values, booleans that disappear, and attributes that merge

Attributes are where this library does more than string formatting, and there are four rules to learn. Values do not have to be quoted, so a variable can sit directly in an attribute position, and several substitutions can share one attribute. Boolean values are attributes: a true boolean renders the attribute and a false one removes it entirely, which is why an example ending in hidden with a false value produces markup with no hidden attribute at all. The class attribute accepts a list of names and joins them, or a dictionary that toggles individual classes, and it can be written more than once, with the values merged from left to right, which is the mechanism for extending a base set of classes with an override. The style attribute takes a dictionary of property names and values and merges the same way.

## Data and aria dictionaries become attributes, and booleans become strings

The last two special cases are the ones that would otherwise be tedious. A dictionary assigned to a data attribute has its keys converted into attribute names by inserting a prefix and a hyphen, so a key like a hyphenated identifier becomes the attribute you would write by hand. The same applies to aria. The one subtlety is boolean handling: inside an aria attribute, a boolean is not dropped when false and not reduced to a bare attribute name when true, because the accessibility specification expects the literal strings, so a true value becomes the string true and a false one the string false. That is a deliberate divergence from the rule that governs ordinary boolean attributes, and the readme flags it with a link to the specification rather than leaving it implicit.

## The readme is a test target, which is why every example is an assertion

The test configuration is the detail that makes the rest of the file trustworthy. The test paths are not just the package: they are the package, the documentation directory, and the readme itself, with a file pattern of a name ending in an underscore and the word test. So every example in the readme you have just read is executed as part of the suite, and a documented output that stops matching the real one breaks the build. The documentation directory is collected for the same reason, using a plugin that runs assertions embedded in prose, which is how a static site can be tested without a separate test file. The default task in the repository's task file is a chain of lint, two format checks, two type checkers and the tests, so the readme is formatted and verified in the same pass as the code.

## One runtime dependency, and a node toolchain that only formats markdown

The dependency picture is small and the tooling picture is not. The package declares a single runtime dependency, the escaping library, and builds with a tool from the same ecosystem as the installer, at a version range bounded on both sides. The development group carries a type checker, a second type checker, a linter, a formatter, the test runner with coverage, an HTML report plugin and a watcher, a documentation generator with a theme and a markdown parser, and a badge generator for the coverage and test badges in the readme. Alongside all that sits a node manifest whose only content is a formatter for markdown with a line width of 80. That manifest also carries a version of 1.0.0 while the Python package is at 0.1.20, which is a good reminder that the two version numbers are unrelated.

## Conclusion

Use tdom if you are already on Python 3.14 and want templates whose interpolated values are escaped by default and whose class lists and style dictionaries can be composed from several sources without string concatenation. Do not adopt it for production work yet, since the project calls itself pre-alpha and says the API may change without warning, and do not expect the documentation to answer your questions, because the site is labelled incomplete. Before you depend on it, read the attribute examples closely, especially the boolean and repeated-attribute rules, pin the exact version, and remember that the runtime dependency count is one.

## FAQ

### What is a t-string in Python?

It is the template string feature added in Python 3.14: it behaves like an f-string but uses a different prefix, and instead of producing a string it produces a template object. tdom is built on that feature, and its render function turns a template into HTML with interpolated values escaped automatically.

### How do I install and try tdom?

You need Python 3.14, which the readme notes was released in October 2025, and then a single pip install command. If you use the uv tool there is a one-line alternative that runs the package in an isolated 3.14 environment without changing anything you already have. The manifest sets 3.14 as a hard floor and also claims support for 3.15.

### How does tdom handle class, style, data and aria attributes?

All four get special handling. Classes accept a list of names or a dictionary that toggles them, and can be repeated and merged from left to right. Styles take a dictionary of property names and values and merge the same way. Data and aria dictionaries have their keys converted into the right attribute names, and booleans inside aria become the strings true and false as the accessibility specification requires.

### Does tdom escape interpolated values, and how is that tested?

The render function escapes interpolated values automatically, and the readme demonstrates it by interpolating a script tag and asserting that the output carries escaped angle brackets and an escaped quote. The test configuration collects the readme as a test target alongside the package and the documentation, so a change in that escaping would fail the suite.

## Sources

- [Official documentation](https://t-strings.github.io/tdom/)
- [Official README](https://github.com/t-strings/tdom#readme)
- [Project repository](https://github.com/t-strings/tdom)
- [Release notes](https://github.com/t-strings/tdom/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/t-strings-tdom
