Type-safe, composable UI components for Python — server-side rendered, HTMX-first, 20+ pre-built components.
Project description
htmforge
Type-safe, composable UI components for Python. Server-side rendered, HTMX-first, framework-agnostic.
Why htmforge?
- Type-safe props via Pydantic v2: props are validated on construction and on assignment.
- Small Element primitives with safe rendering:
Element.to_html()escapes text and maps Python attrs to HTML (e.g.cls→class). - First-class HTMX support: typed enums and helpers for
hx-*attrs. - Framework adapters:
to_fastapi(),to_flask(),to_django()on components for easy integration. - Auto-error injection: Forms automatically bind validation errors to fields via the Form.errors dict.
- 20+ pre-built components: Alerts, DataTables, Modals, Forms, Spinners, Tabs, Toasts, and more.
- Backward compatible: Extend without breaking existing v0.2.x code.
Installation
pip install htmforge
Quickstart (Flask)
Copy-pasteable minimal Flask example using a Page and a DataTable.
from flask import Flask
from htmforge.components.page import Page
from htmforge.components import DataTable
from htmforge.core.element import Element
from htmforge.elements import div, h1
app = Flask(__name__)
class UsersPage(Page):
users: list[list[str]]
def _body_content(self) -> list[Element | str | None]:
return [
div(
h1("Users"),
DataTable(headers=["Name", "Email"], rows=self.users),
)
]
@app.route("/users")
def users():
rows = [["Ada Lovelace", "ada@example.com"]]
return UsersPage(title="Users", users=rows).to_flask()
if __name__ == "__main__":
app.run(debug=True)
Elements
htmforge.elements exposes small factory functions for HTML tags. These
map Pythonic attribute names to HTML and escape text safely. Example:
from htmforge.elements import div, span, input
el = div(
span("Name:"),
input(type="search", name="q", hx_get="/search", cls="search"),
cls="form-row",
)
print(el.to_html())
The hx_get argument renders as hx-get, and text is escaped by
default to prevent XSS.
Components
Layout & Structure
| Component | Description | Import |
|---|---|---|
| Page | Abstract full-page component (adds DOCTYPE) | from htmforge.components.page import Page |
Data Display
| Component | Description | Import |
|---|---|---|
| Alert | Dismissible info/success/warning/error box | from htmforge.components import Alert |
| Badge | Small inline label with variant classes | from htmforge.components import Badge |
| Breadcrumb | Ordered nav with aria-current for current item |
from htmforge.components import Breadcrumb |
| DataTable | Table with dict/list rows, optional HTMX reload, sortable headers | from htmforge.components import DataTable, ColumnDef |
| Pagination | Page links + prev/next, supports HTMX targets | from htmforge.components import Pagination |
| Toast | Timed notifications with OOB swap support | from htmforge.components import Toast |
Navigation & Interaction
| Component | Description | Import |
|---|---|---|
| Accordion | Collapsible sections using <details>/<summary> |
from htmforge.components import Accordion |
| Dropdown | Trigger button with dropdown menu items | from htmforge.components import Dropdown |
| Modal | Trigger button + <dialog> overlay (HTMX body) |
from htmforge.components import Modal |
| SearchInput | Search input with keyup debounce via HTMX |
from htmforge.components import SearchInput |
| Spinner | Accessible loading indicator (SM/MD/LG sizes) | from htmforge.components import Spinner, SpinnerSize |
| Tabs | Tab navigation with HTMX lazy-load | from htmforge.components import Tabs |
Forms & Input
| Component | Description | Import |
|---|---|---|
| FormField | Label + input + optional error block | from htmforge.components import FormField, InputType |
| CheckboxField | Single checkbox with label and error display | from htmforge.components import CheckboxField |
| SelectField | Dropdown select with typed options | from htmforge.components import SelectField |
| RadioGroup | Radio button group with legend and error | from htmforge.components import RadioGroup |
| FormGroup | Container for multiple form fields | from htmforge.components import FormGroup |
| Form | Full form with auto-error injection and HTMX | from htmforge.components import Form |
HTMX integration
Typed enums live in htmforge.htmx: HxSwap, HxTrigger, HxTarget,
and HxPushUrl. They render the correct attribute values. Example:
from htmforge.elements import button
from htmforge.htmx import HxSwap, HxTarget
btn = button(
"Delete",
hx_delete="/items/1",
hx_swap=HxSwap.OUTER_HTML,
hx_target=HxTarget.CLOSEST_TR,
)
Use hx_keyup_delay(ms) to produce keyup delay:{ms}ms trigger strings
for debounced search inputs.
Framework support
FastAPI example:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def index():
return UsersPage(title="Home").to_html()
Flask example (adapter shown above uses to_flask()):
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return UsersPage(title="Home").to_flask()
Django example:
def index(request):
return UsersPage(title="Home").to_django()
Testing
htmforge is fully tested with comprehensive coverage:
- Unit tests: 238 tests passing (+ 5 skipped for optional Django dependency)
- Component tests: Render logic, HTMX attributes, edge cases
- Element tests: Attribute mapping, escaping, void elements
- Framework adapters: FastAPI, Flask, Django with auto-skip for missing dependencies
- Snapshot tests (21): Regression detection via HTML snapshots
- Auto-created in tests/snapshots/ on first run
- Compare rendered HTML on subsequent runs
- Full coverage of all 20+ components
- Performance benchmarks (5): Ensure rendering stays fast
- Element rendering: 1000 iterations <1s
- Nested elements: 1000 iterations <1s
- DataTable (10 rows): 1000 renders <2s
- Alert component: 1000 renders <1s
- render() helper: 1000 calls <1s
Run tests:
pytest # All tests
pytest -v # Verbose output
mypy htmforge/ --strict # Type check (strict mode, 22 files)
ruff check htmforge/ # Lint
ruff format --check htmforge/ # Format check
License
This project is licensed under the MIT License with the Commons Clause
condition. It is free for personal projects, open source projects, and
small businesses (see LICENSE). Organizations with annual revenue or
funding over USD 1,000,000 or more than 100 employees require a
separate commercial license from the author.
Contributing
See CONTRIBUTING.md and the docs site at
https://mondi04.github.io/htmforge/contributing/ for contribution guidelines.
Project details
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
File details
Details for the file htmforge-0.3.0.tar.gz.
File metadata
- Download URL: htmforge-0.3.0.tar.gz
- Upload date:
- Size: 2.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6df3c2488b5cb723a50c4a7de4ca218dc63bd7748ebe61069f73925434328d67
|
|
| MD5 |
f4e61b4e6de3d7d7c53eb8a2bba88eeb
|
|
| BLAKE2b-256 |
8289f50ca2dd0113b6224a02407f5da35b6e9cfaedceb5472a898bfac2c3dd04
|
File details
Details for the file htmforge-0.3.0-py3-none-any.whl.
File metadata
- Download URL: htmforge-0.3.0-py3-none-any.whl
- Upload date:
- Size: 36.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c3e6c2ea159b8027f8614c738f4a874d215567daa42b4d4811fbd431a5d6632
|
|
| MD5 |
0bd5236a0b10d431f784356649208c7f
|
|
| BLAKE2b-256 |
31a3402e60493bfff48403fc0abcc507e4f65fc4229fa45b2eb43ac96b3ebc6d
|