Plain Web Components
Home-UI is a set of custom elements and one class sheet. There is nothing to
initialise and no framework to adopt: register the elements with one module, write hui-* markup,
and the browser does the rest.
Load the two files
Section titled “Load the two files”<!doctype html><html lang="en" class="light"> <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"> <hui-button>It works</hui-button> </body></html>class="light" or class="dark" pins the theme server-side, so there is no flash of the wrong
one. hui-surface opts the page itself into the tokens; see Dark mode and
Colours and tokens.
Those URLs load the current build from this site. For something you ship, download the files and serve them yourself - see Getting the files.
Configure with attributes
Section titled “Configure with attributes”A component is configured by attributes, so any server or template can write it:
<hui-input name="email" type="email" required aria-label="Email"></hui-input>
<hui-select name="plan" value="pro"> <div role="option" data-value="free">Free</div> <div role="option" data-value="pro">Pro</div></hui-select>Anything the server owns - the options, the current value, an invalid state - is plain markup.
Read values and events
Section titled “Read values and events”Every form-associated control takes name, value and disabled, and submits in its ancestor
<form> exactly like a native control. new FormData(form) and an ordinary POST both carry it, so
there is no JavaScript adapter between the element and the endpoint.
Native input and change events fire as usual, and each element also emits a bubbling, composed
hui-* event for what the native ones do not cover:
const input = document.querySelector('hui-input');input.addEventListener('input', () => console.log(input.value));
document.querySelector('hui-select').addEventListener('hui-change', (event) => { console.log(event.detail.value);});The markup is the state
Section titled “The markup is the state”The components hold no application state. When the server sends new HTML, replace the subtree and the elements read the new markup; when a control changes, the page decides what that means. No endpoint, token or record id belongs in an attribute - that is the server’s job. The full model is in server-side mechanics.
The package ships a Remix Icon sprite as dist/hui-icons.svg. Serve it beside the other 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>The same elements from a server-rendered stack: Go templates, ASP.NET Core, Python and PHP. If you also use HTMX or Alpine, HTMX forms, HTMX swaps and Alpine cover the wiring.