Build your first custom widget
The custom widget path is the fastest way onto the overlay: a folder with an
index.html and a widget.json becomes a live, hot-reloaded hub — the panel chrome,
drag, hover-reveal, and auto-collapse are drawn for you. No build step, no TypeScript. It’s
locked down by default — it gets a limited set of capabilities declared in its
widget.json (and can run a restricted Node.js backend), but it can’t extend TeamHUD’s
internals or reach native code. For full trust, build a
plugin.
This is also the path the in-app AI chat uses when you ask it to build a widget.
Before you start
Section titled “Before you start”- The TeamHUD app running.
- Nothing else — you edit plain HTML/CSS/JS.
1. Create the folder
Section titled “1. Create the folder”Custom widgets live in a watched folder. Your personal one survives app updates:
~/.teamhud/custom-widgets/my-widget/ widget.json index.htmlThe overlay picks up the folder within ~1.5 s — no restart. If it doesn’t appear, enable it from the widget library.
2. Write the manifest
Section titled “2. Write the manifest”widget.json is the only required config beyond your HTML:
{ "name": "My Widget", // hub label "html": "index.html", // your UI (local file or an https:// URL) "icon": "icon.svg", // optional hub icon — SVG only "size": { "width": 300, "height": 220 }, "permissions": { "backend": false, "node": [], "network": false }}3. Write the UI
Section titled “3. Write the UI”Two rules make your widget match the HUD by construction:
- Use
--th-*tokens for color, type, spacing, and radius — never hardcode hex, px font sizes, or font-family literals. The host injects a live snapshot of every token and re-pushes them when the user changes density, accent, or theme, sovar(--th-…)tracks the HUD automatically. - Mark clickable areas
class="teamhud-interactive"so the click-through overlay lets pointer events reach them.
A starter index.html:
<!doctype html><body style="margin: 0; font-family: var(--th-font-ui); color: var(--th-text-primary);"> <div id="app" class="teamhud-interactive" style="padding: 12px; display: flex; flex-direction: column; gap: 8px;"> <div style="font-size: var(--th-text-sm); color: var(--th-text-muted);">Clicks</div> <div id="count" style="font-size: var(--th-text-xl);">0</div> <button id="bump" style="font: inherit; padding: 6px 10px; border-radius: var(--th-radius-md); border: 1px solid var(--th-border-secondary); background: var(--th-bg-surface); color: var(--th-text-primary);"> Count up </button> </div> <script> let n = 0; const out = document.getElementById('count'); document.getElementById('bump').addEventListener('click', () => { out.textContent = String(++n); }); </script></body>Save, and the overlay hot-reloads. Common tokens: --th-text-primary / --th-text-muted
(text), --th-text-sm/-md/-lg/-xl (sizes), --th-bg-surface / --th-bg-hover
(surfaces), --th-border-secondary (borders), --th-radius-md (corners),
--th-space-1…--th-space-6 (spacing), --th-accent (accent). The full catalog, the
shared .th-* classes/elements, and the rules are in
Theming & styling.
What you built
Section titled “What you built”A locked-down overlay widget that themes itself to the HUD automatically. When you need to extend TeamHUD’s internals or run with full trust, cross over to building a plugin.
Next steps
Section titled “Next steps”- Let the AI do it: ask the in-app AI chat to build a widget — it authors this same folder shape behind a confirmation card.
- Build your first plugin — the full TypeScript/Lit path.