Skip to content
Home Theme Gallery

Python

Home-UI is server-rendered HTML plus two static files, so it fits Django or Flask with no client build: a base template loads the pair, the template writes the elements, and the view reads their values on POST.

A Django base template:

<!doctype html>
<html lang="en" class="{{ theme }}">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://home-ui.harriverse.com/dist/latest/home-ui.css" />
<script type="module" src="https://home-ui.harriverse.com/dist/latest/home-ui.js"></script>
</head>
<body class="hui-surface">
{% block content %}{% endblock %}
</body>
</html>

In Flask, Jinja2 is the same shape with url_for('static', filename='home-ui.css') if you self-host. class="light" or class="dark" is read server-side so there is no flash of the wrong theme; hui-surface opts the page into the tokens (see Dark mode).

Those URLs load the current build from this site. For something you ship, download the files and serve them yourself - see Getting the files.

A component is configured entirely by attributes, so a template writes them like any other tag:

<hui-field>
<span data-label>Project name</span>
<hui-input data-control name="project" value="{{ project.name }}"
{% if errors.project %}aria-invalid="true"{% endif %}></hui-input>
{% if errors.project %}<span data-error>{{ errors.project }}</span>{% endif %}
</hui-field>

Every form-associated control submits under its name, so the view reads it like any native field:

# Django
def save(request):
if request.method == "POST":
project = request.POST["project"]
volume = request.POST["volume"] # from hui-slider, a string
# Flask
@app.post("/save")
def save():
project = request.form["project"]
volume = request.form["volume"]

A hui-slider in range mode submits one joined "low,high" value under its name, as a native control would.

Business validation is the server’s. On a failed POST, re-render the same template with the submitted values and an error per field; the template above marks the control aria-invalid and wires the message to it. The client does not reconstruct that.

<form method="post">
{% csrf_token %}
<!-- hui-field, hui-input and the rest -->
<hui-button type="submit">Save</hui-button>
</form>

hui-button type="submit" drives the ancestor form, so it can replace <button type="submit"> wherever you want the component’s styling.

To update part of the page, render a fragment and swap it in. Any technique works - fetch, HTMX, or a framework of your own; the element is created from the new markup and re-associates with its form. Keep endpoints, tokens and record ids on the server: there is no attribute for them, deliberately. The full model is in server-side mechanics.