SkillAgentSearch skills...

Htmy

Async, pure-Python server-side rendering engine for hypermedia applications.

Install / Use

/learn @volfpeter/Htmy

README

Tests Linters Documentation PyPI package

Source code: https://github.com/volfpeter/htmy

Documentation and examples: https://volfpeter.github.io/htmy

htmy

Async, pure-Python server-side rendering engine.

Unleash your creativity with the full power and Python, without the hassle of learning a new templating language or dealing with its limitations!

Key features

  • Async-first, to let you make the best use of modern async tools.
  • Powerful, React-like context support, so you can avoid prop-drilling.
  • Sync and async function components with decorator syntax.
  • All baseline HTML tags built-in.
  • Async HTML streaming support for optimal TTFB (time to first byte).
  • Support for native HTML/XML documents with dynamic formatting and slot rendering, without custom syntax.
  • Markdown support with tools for customization.
  • Async, JSON based internationalization.
  • Built-in, easy to use ErrorBoundary component for graceful error handling.
  • Unopinionated: use the backend, CSS, and JS frameworks of your choice, the way you want to use them.
  • Everything is easily customizable, from the rendering engine to components, formatting and context management.
  • Automatic and customizable property-name conversion from snake case to kebab case.
  • Compatible with any other templating library through wrappers.
  • Fully-typed.

Testimonials

"Thank you for your work on fasthx, as well as htmy! I've never had an easier time developing with another stack." (ref)

"One of the main parts of the FastAPI -> fasthx -> htmy integration I'm falling in love with is its explicitness, and not too much magic happening." (ref)

"Thank you for your work on htmy and fasthx, both have been very pleasant to use, and the APIs are both intuitive and simple. Great work." (ref)

"I love that the language-embedded HTML generation library approach is becoming more popular." (ref)

"Neat approach and it naturally solves the partial templates problem 👍" (ref)

"Great API design!" (ref)

Support

Consider supporting the development and maintenance of the project through sponsoring, or reach out for consulting so you can get the most out of the library.

Installation

The package is available on PyPI and can be installed with:

$ pip install htmy

The package has the following optional dependencies:

  • lxml (recommended): When installed, it is prioritized over xml.etree.ElementTree and provides more secure, faster, and more flexible HTML and XML processing. It is used, for example, for Markdown processing. Install with: pip install "htmy[lxml]".

Component libraries

Concepts

The entire library -- from the rendering engine itself to the built-in components -- is built around a few simple protocols and a handful of simple utility classes. This means that you can easily customize, extend, or replace basically everything in the library. Yes, even the rendering engine. The remaining parts will keep working as expected.

Also, the library doesn't rely on advanced Python features such as metaclasses or descriptors. There are also no complex base classes and the like. Even a junior engineer could understand, develop, and debug an application that's built with htmy.

Components

Every object with a sync or async htmy(context: Context) -> Component method is an htmy component (technically an HTMYComponentType). Strings are also components, as well as lists or tuples of HTMYComponentType or string objects. In many cases though, you don't even need to create components, simple functions that return components will be sufficient -- you can find out more about this in the Components guide of the documentation.

Using the htmy() method name enables the conversion of any of your pre-existing business objects (from TypedDictss or pydantic models to ORM classes) into components without the fear of name collision or compatibility issues with other tools.

Async support makes it possible to load data or execute async business logic right in your components. This can reduce the amount of boilerplate you need to write in some cases, and also gives you the freedom to split the rendering and non-rendering logic in any way you see fit.

Example:

from dataclasses import dataclass

from htmy import Component, Context, html

@dataclass(frozen=True, kw_only=True, slots=True)
class User:
    username: str
    name: str
    email: str

    async def is_admin(self) -> bool:
        return False

class UserRow(User):
    async def htmy(self, context: Context) -> Component:
        role = "admin" if await self.is_admin() else "restricted"
        return html.tr(
            html.td(self.username),
            html.td(self.name),
            html.td(html.a(self.email, href=f"mailto:{self.email}")),
            html.td(role)
        )

@dataclass(frozen=True, kw_only=True, slots=True)
class UserRows:
    users: list[User]
    def htmy(self, context: Context) -> Component:
        # Note that a list is returned here. A list or tuple of `HTMYComponentType | str` objects is also a component.
        return [UserRow(username=u.username, name=u.name, email=u.email) for u in self.users]

user_table = html.table(
    UserRows(
        users=[
            User(username="Foo", name="Foo", email="foo@example.com"),
            User(username="Bar", name="Bar", email="bar@example.com"),
        ]
    )
)

htmy also provides a powerful @component decorator that can be used on sync or async my_component(props: MyProps, context: Context) -> Component functions and methods to convert them into components (preserving the props typing). You can find out more about this feature in the Function components guide.

Here is the same example as above, but with function components:

from dataclasses import dataclass

from htmy import Component, Context, component, html

@dataclass(frozen=True, kw_only=True, slots=True)
class User:
    username: str
    name: str
    email: str

    async def is_admin(self) -> bool:
        return False

@component
async def user_row(user: User, context: Context) -> Component:
    # The first argument of function components is their "props", the data they need.
    # The second argument is the rendering context.
    role = "admin" if await user.is_admin() else "restricted"
    return html.tr(
        html.td(user.username),
        html.td(user.name),
        html.td(html.a(user.email, href=f"mailto:{user.email}")),
        html.td(role)
    )

@component
def user_rows(users: list[User], context: Context) -> Component:
    # Nothing to await in this component, so it's sync.
    # Note that we only pass the "props" to the user_row() component (well, function component wrapper).
    # The context will be passed to the wrapper during rendering.
    return [user_row(user) for user in users]

user_table = html.table(
    user_rows(
        [
            User(username="Foo", name="Foo", email="foo@example.com"),
            User(username="Bar", name="Bar", email="bar@example.com"),
        ]
    )
)

Built-in components

htmy has a rich set of built-in utilities and components for both HTML and other use-cases:

  • html module: a complete set of baseline HTML tags.
  • Snippet and Slots: utilities for creating dynamic, customizable document snippets in their native file format (HTML, XML, Markdown, etc.), with slot rendering support.
  • md: MarkdownParser utility and MD component for loading, parsing, converting, and rendering markdown content.
  • i18n: utilities for async, JSON based internationalization.
  • Tag, TagWithProps, and wildcard_tag: base classes and utilities for creating custom XML tags.
  • ErrorBoundary, Fragment, SafeStr, WithContext: utilities for error handling, component wrappers, context providers, and formatting.
  • etree.ETreeConverter: utility that converts XML to a component tree with support for custom HTMY components.

Rendering

htmy.Renderer is the built-in, default renderer of the library.

If you're using the library in an async web framework like FastAPI, then you're already in an async environment, so you can render components as simply as this: await Renderer().render(my_root_component).

If you're trying to run the renderer in a sync environment, like a local script or CLI, then you first need to wrap the renderer in an async task and execute that task with asyncio.run():

import asyncio

from htmy import Renderer, html

async def render_page() -> None:
    page = (
        html.DOCTYPE.html,
        html.html(
            html.body(
                html.h1("Hello World!"),
                html.p("This page was rendered by
View on GitHub
GitHub Stars391
CategoryDevelopment
Updated13h ago
Forks9

Languages

Python

Security Score

100/100

Audited on Mar 29, 2026

No findings