VOOZH about

URL: https://dev.to/mondi/type-safe-html-in-python-no-templates-no-runtime-surprises-f1h

⇱ Type-safe HTML in Python — no templates, no runtime surprises - DEV Community


Type-safe HTML in Python — no templates, no runtime surprises

Been building FastAPI apps for a while.
Every time the same thing: Jinja2 works until it doesn't.

Misspelled variable? Silent empty cell. Missing context key? Crash on render.
No mypy, no autocomplete, no safety net.

I wanted something different, so I built htmforge —
HTML entirely in Python, validated by Pydantic v2.


Before

{# users.html #}
{% for user in users %}
 <td>{{ user.naem }}</td> {# typo — silent empty cell #}
 <td>{{ user.role }}</td> {# wrong variant? find out at runtime #}
{% endfor %}

After

from htmforge.components import DataTable, ColumnDef, Badge, BadgeVariant

DataTable(
 columns=[
 ColumnDef(key="name", label="Name"),
 ColumnDef(key="role", label="Role"),
 ],
 dict_rows=[
 {
 "name": user["name"],
 "role": Badge(
 text=user["role"].title(),
 variant=BadgeVariant.DANGER
 if user["role"] == "admin"
 else BadgeVariant.SUCCESS,
 ),
 }
 for user in users
 ],
)

Typo in "name"? Pydantic catches it on startup.
Wrong BadgeVariant? mypy catches it.
XSS? Escaped automatically — no | safe to misuse.


HTMX is typed too

from htmforge.htmx import HxSwap, HxTarget
from htmforge.elements import button

button(
 "Delete",
 hx_delete=f"/users/{user_id}",
 hx_swap=HxSwap.OUTER_HTML,
 hx_target=HxTarget.CLOSEST_TR,
)

No more "outerHTML" string guessing. Full autocomplete.


Stack

  • Pydantic v2 — prop validation on construction and assignment
  • MarkupSafe — XSS protection, automatic, no opt-out
  • 20+ components — DataTable, Form, Modal, Toast, Tabs, Accordion...
  • Framework adapters — FastAPI, Flask, Django built in
  • 240 tests, mypy strict, ruff clean

Looking for contributors

The project is early but functional. Good first issues are documented
in CONTRIBUTING.md — from small things like adding a lang attribute
to Page, to rendering improvements in DataTable.

If this approach interests you: