Shared Web Components with theming for use across HOTOSM tools.
📖 Documentation:
https://ui.hotosm.org
🖥️ Source Code:
https://github.com/hotosm/ui
🎯 Roadmap / Tasks: https://github.com/orgs/hotosm/projects/37/views/3
Shared Web Components built with Lit and themed for HOTOSM tools using WebAwesome primitives.
Goals:
- Reduce code duplication across HOT tools.
- Provide a consistent HOT look-and-feel out of the box.
- Keep the component set small and focused (header, sidebar, footer, etc.).
WebAwesome version: 3.11.0
pnpm install @hotosm/ui @awesome.me/webawesome@3.11.0Note
@awesome.me/webawesome is a peer dependency. Always pin it to the
version shown above to avoid conflicts between HOT components and
WebAwesome internals.
Use the single self-contained stylesheet (WebAwesome base styles + HOT theme inlined):
import "@hotosm/ui/dist/style.css";Or from CDN:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@hotosm/ui@1.0.0/dist/style.css" />style-core.css (HOT theme only, no WebAwesome) exists for pages that
load the WebAwesome base stylesheets themselves - most apps don't need it.
There are none to load. Since 2026-08-27 the design system uses the platform's own sans stack - no Archivo, no Barlow, no webfonts at all.
See MADR 0014 for the rationale.
Add the required WebAwesome classes to your <html> element:
<!DOCTYPE html>
<html class="wa-theme-default wa-palette-hotosm">
<head>
...
</head>
<body>
...
</body>
</html>Important
These classes activate the HOT colour palette and light theme for every WebAwesome component on the page. Do not remove them.
Recommended: register all WebAwesome elements once, up front, so every custom element is defined before first render - this eliminates flash of undefined custom elements (FOUCE) without maintaining per-page import lists:
import "@hotosm/ui/dist/style.css";
import "@hotosm/ui/dist/webawesome-all.js"; // registers every wa-* element
import "@hotosm/ui/dist/components/header/header.js"; // hot-* components you use<hot-header title="My App"></hot-header>@hotosm/ui/vite provides build helpers for Vite 5, 7, and 8.
Add the circular chunk guard to catch chunk cycles that can cause a blank page at runtime:
// vite.config.ts
import { circularChunkGuard } from "@hotosm/ui/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [circularChunkGuard()],
});Keep the guard enabled even if you do not split chunks manually. Dependency updates can also introduce cycles.
You can also place WebAwesome in its own chunk. This lets browsers keep it cached when the rest of your app changes:
For Vite 8:
// vite.config.ts
import { WEBAWESOME_CHUNK } from "@hotosm/ui/vite";
import { defineConfig } from "vite";
export default defineConfig({
build: {
rolldownOptions: {
output: {
codeSplitting: {
groups: [WEBAWESOME_CHUNK],
},
},
},
},
});For Vite 5-7:
// vite.config.ts
import { matchWebawesome } from "@hotosm/ui/vite";
import { defineConfig } from "vite";
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: (id) => matchWebawesome(id),
},
},
},
});This cache is per origin, so it is reused across deployments of the same app, not across different HOT tools.
Load the raw dist files, with an import map to resolve the bare
@awesome.me/webawesome/... imports in webawesome-all.js. The map's
WebAwesome pin must match the version this @hotosm/ui release was
built against. Do not use jsDelivr's /+esm URLs - they duplicate
element registrations and most wa-* elements never register.
<!DOCTYPE html>
<!-- wa-cloak: hides the page until all custom elements are defined (2s max) -->
<html class="wa-theme-default wa-palette-hotosm wa-cloak">
<head>
<!-- Fonts: see "Fonts" above -->
<!-- WebAwesome base styles + HOT theme, self-contained -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@hotosm/ui@1.0.0/dist/style.css" />
<script type="importmap">
{
"imports": {
"@awesome.me/webawesome/dist/components/": "https://cdn.jsdelivr.net/npm/@awesome.me/webawesome@3.11.0/dist-cdn/components/"
}
}
</script>
<!-- Register every wa-* element, then the hot-* components -->
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@hotosm/ui@1.0.0/dist/webawesome-all.js"
></script>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@hotosm/ui@1.0.0/dist/hotosm-ui.js"
></script>
</head>
<body>
<hot-header id="hdr" title="My App" size="s" show-login></hot-header>
<script>
// Boolean props default to false.
// Include the attribute to make it true.
const hdr = document.getElementById("hdr");
hdr.drawer = true; // enable the hamburger drawer
</script>
</body>
</html>For multi-page (HTMX) apps, also inline a few critical theme tokens in
<head> so first paint looks right before the CDN CSS arrives - see
Loading Strategies.
Web Components work in React with a small caveat - use ref callbacks
for custom events if React's synthetic event system doesn't forward them:
import "@hotosm/ui/dist/components/header/header.js";
function App() {
return (
<hot-header
title="My App"
ref={(el) => {
if (el) el.addEventListener("login", () => console.log("logged in"));
}}
/>
);
}The HOT theme exposes CSS custom properties you can reference in your own stylesheets:
.my-card {
background: var(--hot-color-primary-50);
color: var(--hot-color-neutral-900);
font-family: var(--hot-font-sans);
padding: var(--hot-spacing-medium);
border-radius: var(--hot-border-radius-large);
}See all available tokens in
src/themes/hot.css.
- Header: see
header.mdfor detailed integration, styling, and framework examples.
git clone git@github.com:hotosm/ui.git
cd ui
pnpm install
pnpm run dev # starts Storybook on localhost:3001Styling files (under src/themes/):
| File | Purpose |
|---|---|
hot.css |
HOT design tokens (colours, typography, spacing) |
hot-wa.css |
WebAwesome variable overrides to apply the HOT palette |
HOT UI is free and open source software. You may use any HOT UI project under the terms of the GNU General Public License (GPL) Version 3.
