Go templates
Home-UI is designed for a Go host that renders HTML with html/template. There is no component runtime to initialise and no build step on the host side. A page declares two files and is done:
<!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"> {{ template "content" . }} </body></html>{{ .Theme }} is "light" or "dark", read from the cookie 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.
Components are attributes
Section titled “Components are attributes”A component is configured entirely by attributes, so a Go template writes them the same way it writes 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"{{ end }}></hui-input> {{ with .Errors.Project }} <span data-error>{{ . }}</span> {{ end }}</hui-field>Two things are worth noticing. The control’s name and value are plain template data. And the invalid state is aria-invalid — there is no error attribute and no error property, because invalid is a state every control already understands. hui-field wires the label, description and error to the control for you.
Validation errors after a POST
Section titled “Validation errors after a POST”Business validation is the server’s. A POST handler re-renders the form with the submitted values and the error text, and the same template above produces a control marked invalid and an error message that is wired to it. The client does not have to reconstruct that on its own.
type ProjectForm struct { Name string Errors map[string]string}An hx-post returning that partial is swapped in place; the controls are re-created and re-associated with the form. See HTMX forms and HTMX swaps.
Value controls
Section titled “Value controls”Every form-associated control takes name, value and disabled:
<hui-combobox name="courseId" value="{{ .CourseID }}" label="Course"></hui-combobox><hui-date-picker name="raceDate" value="{{ .RaceDate }}"></hui-date-picker><hui-slider name="volume" min="0" max="100" value="{{ .Volume }}"></hui-slider><hui-checkbox name="agree" value="yes" {{ if .Agree }}checked{{ end }}></hui-checkbox>hui-button type="submit" drives the ancestor form, so a template that wants the component’s styling can use it in place of <button type="submit">.
The package ships a Remix Icon sprite as dist/hui-icons.svg. Copy it beside the other two files and reference a symbol with <use>; no JavaScript is involved:
<svg width="16" height="16" aria-hidden="true"> <use href="/static/hui-icons.svg#ri-check-line"></use></svg>What not to do
Section titled “What not to do”Do not put endpoints, tokens or application state into a component’s attributes. There is no attribute for them, and that is deliberate:
<!-- rejected --><trainer-selector api="/api/trainers" auth-token="…"></trainer-selector>The host owns fetching, routing, validation and persistence. A combobox does not search; it emits events and the host searches with HTMX. Keeping that boundary is what lets the same library serve a Go host and a static preview like this one.