T-HTML (Components)¶
What is T-HTML?¶
T-HTML is a small DSL on top of PEP 750 t-strings. It adds one rule to plain HTML templates: a tag whose name starts with an uppercase letter is treated as a component call instead of a literal HTML element.
When the renderer sees <Card title="hello">, it looks up Card as a Python callable, passes title="hello" as a keyword argument, normalizes the nested content, and passes it as children. That is the whole model. There is no virtual DOM, no reactivity, no build step.
The thtml-tstring package provides this on top of the same parser and escaping used by html-tstring.
Defining components¶
A component is any callable that takes keyword arguments and returns something renderable. With @component, you can return a Template directly:
from string.templatelib import Template
from thtml_tstring import component
@component
def Button(*, children: str, kind: str = "primary") -> Template:
return t'<button class="btn btn-{kind}">{children}</button>'
The @component decorator checks the return value. If it is a Template, the decorator auto-wraps it into a Renderable (the same thing thtml() produces). If it is already a Renderable, RawHtml, str, or another supported type, it passes through as-is.
The decorator is a no-op for typing purposes. @component is @component(backend="thtml") by default.
Renderable¶
Renderable is a shared type that represents safe, render-ready HTML. It is produced by thtml(), html() (from html-tstring), or the @component auto-wrap.
Unlike RawHtml (which takes an arbitrary external string on trust), a Renderable is always built from a parsed and validated template. The renderer can embed it as HTML without escaping.
from thtml_tstring import thtml
# thtml() returns a Renderable
result = thtml(t"<div>hello</div>")
html = result.render()
Rendering with thtml() and render_html()¶
thtml() creates a Renderable. Call .render() to get the HTML string, or pass it to render_html():
from html_tstring import render_html
from thtml_tstring import thtml
result = thtml(t"<Button kind='success'>Save</Button>")
# either works
html = result.render()
html = render_html(result)
The existing eager API thtml_tstring.html() still works and returns a str directly:
from thtml_tstring import html
# eager, returns str
page = html(t"<Button kind='success'>Save</Button>")
Component resolution¶
By default, component names are looked up in the caller's local and global scope:
from thtml_tstring import thtml
# Button is found in the current scope automatically
result = thtml(t"<Button>Save</Button>")
You can also pass the scope explicitly, which is useful in tests:
Lookup order: locals first, then globals. The resolved name must be callable.
For thtml(), the scope is captured at creation time, not at render time. If the caller frame is not available, a TemplateRuntimeError is raised with a message to pass globals=/locals= explicitly.
How components are called¶
Each attribute becomes a keyword argument. Children are normalized before they are passed as children=: strings stay strings, Renderable and RawHtml stay HTML-capable values, Fragment and iterables flatten recursively, and None disappears. Spread attributes are merged into the keyword arguments.
Attribute names are forwarded as-is from the template. class, aria-label, and other names that are not valid Python identifiers are not renamed automatically. Components that need those values should accept **props.
Spread attributes on components¶
from thtml_tstring import thtml
props = {"kind": "danger", "aria-label": "Delete"}
result = thtml(t"<Button {props}>Delete</Button>")
# Button(**{"kind": "danger", "aria-label": "Delete", "children": "Delete"})
Component return values¶
Components can return:
Template: auto-wrapped intoRenderableby@componentRenderable: passed through as-isRawHtml: inserted without escaping (for external trusted HTML)str: escaped as text (not raw HTML)Fragment,list,tuple: recursively flattened and renderedNone: empty outputint,float,bool: converted to escaped text
The recommended pattern is to return a Template:
from string.templatelib import Template
from thtml_tstring import component
@component
def Stack(*, children: str) -> Template:
return t"<section>{children}</section>"
If you need more control, return a Renderable explicitly:
from thtml_tstring import Renderable, component, thtml
@component
def Stack(*, children: str) -> Renderable:
return thtml(t"<section>{children}</section>")
Both produce the same output.
Nested components¶
Components can contain other components:
from string.templatelib import Template
from thtml_tstring import component, thtml
@component
def Card(*, children: str, title: str) -> Template:
return t"""
<div class="card">
<h2>{title}</h2>
<div class="card-body">{children}</div>
</div>
"""
@component
def Badge(*, children: str) -> Template:
return t'<span class="badge">{children}</span>'
label = "new"
result = thtml(t"<Card title='Status'><Badge>{label}</Badge></Card>")
html = result.render()
Decorator vs explicit wrap¶
Use @component for the common case. The decorator auto-wraps Template returns, so component definitions stay short.
Use thtml() (or html() from html-tstring) explicitly when you need to:
- Control which scope is used for component resolution
- Fix the backend (
"html"vs"thtml") - Build a
Renderableoutside a component function
from thtml_tstring import Renderable
@component
def Badge(*, children: str, tone: str = "info") -> Renderable:
# explicit scope control
return thtml(
t'<span class="badge badge-{tone}">{children}</span>',
globals={"InnerComponent": InnerComponent},
)
RawHtml¶
RawHtml is for injecting external trusted HTML strings that did not come from a t-string template. Typical uses: SVG icons loaded from files, HTML from a sanitizer, third-party widget markup.
from html_tstring import RawHtml, render_html
icon = RawHtml('<svg><circle r="5"/></svg>')
render_html(t"<div>{icon}</div>")
For component composition, use @component + Template returns or thtml() instead of RawHtml.
Low-level APIs¶
thtml-tstring also exposes the same tooling functions as html-tstring:
from thtml_tstring import check_template, format_template, compile_template
check_template(t"<Button />") # validate T-HTML syntax
format_template(t"<Button />") # canonical formatting
See also¶
- HTML usage for escaping, attributes, and spread details
- Error Handling
- API Reference