Application components
This section summarizes the different components of holm
applications. If you have used Next.js before, you will find these concepts very familiar. If not, don't worry, they may seem complex at first, but they are quite intuitive and natural.
Layouts and pages
Layouts are defined in the layout.py
modules of packages as a callable layout
variable (note that for example classes with an __init__()
method, or htmy.Component
s are callable, so they also qualify).
Rules for layouts:
- The root layout must always return a
htmy.Component
. - The
layout
variable must be a callable and it must accept a positional argument (other thanself
if the layout is a method of a class), the data / properties of the layout that are returned by the pages or layouts this layout directly wraps. - The
layout
variable can have additional arguments (position or keyword and keyword-only, but not positional-only). These arguments must be FastAPI dependencies. They will be automatically resolved during each request. - Returning a tuple or a list from a layout is not allowed unless the value is a
htmy.ComponentSequence
. Tuples and lists are always interpreted and treated as component sequences, so you don't need to track what kinds of components pages and layouts return. Seehtmy.is_component_sequence()
for more information.
Layouts automatically wrap all layouts and pages in subpackages.
Tip: layouts can provide context for their entire subtree by wrapping the subtree with a htmy
ContextProvider
component.
Pages are defined in the page.py
module of packages as a callable page
variable (note that classes with an __init__()
method are callable, so they also qualify).
Rules for pages:
- The
page
variable must be a FastAPI dependency, meaning it can have any arguments as long as they can all be resolved by FastAPI as dependencies. - The
page
variable must return the properties object for the layout that directly wraps it. - If a page is not wrapped by a layout, then it must return a
htmy.Component
. - Returning a tuple or a list from a page is not allowed unless the value is a
htmy.ComponentSequence
. Tuples and lists are always interpreted and treated as component sequences, so you don't need to track what kinds of components pages and layouts return. Seehtmy.is_component_sequence()
for more information.
page.py
modules can have a metadata
variable, which can be an arbitrary mapping or a FastAPI dependency that returns an arbitrary mapping.
If the metadata
variable is a callable, it can have any FastAPI dependencies, even different ones than what the page
or wrapper layouts use.
The metadata provided by the currently served page is made available to every htmy.Component
(from the root layout to the page itself) through the built-in Metadata
utility. It can be accessed as Metadata.from_context(context)
where context
is the htmy
rendering context which is passed to every component. See the htmy documentation for more information.
This feature is particularly useful when page-specific information - for example title, description, or keywords - must be set dynamically (for example, in layouts) on a page-by-page basis. Metadata
implements the Mapping
protocol, so once loaded from the htmy
rendering context in a component with metadata = Metadata.from_context(context)
, you can use it simply like this to set page-specific information in any layout, component, or the page itself: htmy.html.title(metadata["title"])
.
APIs
APIs are defined in the api.py
module or packages as an APIRouter
variable or a callable that returns an APIRouter
.
These are standard FastAPI APIRouter
s, but you should still follow these recommendations:
- If routes in the API don't do any rendering, then the
api()
callable should have no arguments or simply anapi
variable should be used. Otherwise theapi()
callable should have a singlefasthx.htmy.HTMY
positional argument. The application's renderer will be passed to this function automatically byholm
. - If an
api()
callable is used, it may have further arguments as long as they all have default values. This pattern can simplify API testing for example, by allowing custom configurations for tests.
Note on rendering APIs:
- The primary role of these APIs is to return so called "partials" (small HTML snippets instead of entire HTML pages) to clients such as HTMX for swapping.
- The components returned by rendering routes are not automatically wrapped in layouts like pages are.
Path parameters as package names
You can capture URL path parameters by using special package names. Two formats are supported:
_param_
format: package names like_id_
or_user_id_
.{param}
format: package names like{id}
or{user_id}
. (Note: it works withholm
, but static code analysis tools flag it because{id}
is not a valid Python identifier.)
Both formats are converted to FastAPI path parameters. For example:
- File path
user/_id_/profile
becomes URL/user/{id}/profile
. - File path
user/{user_id}/settings
becomes URL/user/{user_id}/settings
.
Any layout or page within these packages can access the parameter as a FastAPI dependency by adding it to their function signature (e.g., id: int
or user_id: str
).
Private packages
You can prefix package names with one or more underscores to opt the entire package (including its subpackages) out of the automatic application component discovery.
For example, if you have a package named _private
somewhere in your application folder, you can freely place valid page, layout, or API files within it or its subpackages. These modules will not be processed and included in the application.
Reminder: if the package name also ends with an underscore, it will be treated as a path parameter, as described above.
Error handling
Error handlers are defined in the error.py
module of the root package as a handlers
variable, which can be a mapping from exception types or HTTP status codes to standard FastAPI exception handler functions, or a callable that expects a fasthx.htmy.HTMY
positional argument and returns such a mapping. The latter option is useful if you want to do custom HTML rendering in an error handler.
Error handlers must return either a htmy.Component
or a fastapi.Response
. Responses are returned to the client as is, and components are automatically rendered and returned as a HTMLResponse
.
It is important to know that rendered errors are not automatically wrapped in the root layout of your application. The main reason for this is holm
can not always know what the client (be it HTMX or simply the browser) expects.
The recommended way to handle this is to create an HTML skeleton component somewhere in your codebase, and use it both in the root layout and in error handlers to wrap page content. This, together with application-specific exceptions make it easy to handle every error correctly with minimal manual effort.
Also, you can simply return redirect responses in error handlers that navigate users to the appropriate error page, passing context as query parameters.