Theming & styling
Every widget — custom or plugin — styles itself from TeamHUD’s --th-* design tokens, so
it matches the HUD by construction and follows the user’s density, accent, and theme
automatically. The one meta-rule:
If you’re about to write a hex color, a pixel value, an animation literal, drag math, or panel placement — stop. A token or component already owns it.
The density model
Section titled “The density model”- One base unit scales everything.
--th-uis a user-chosen pixel value (default10pxCompact /14pxNormal). Spacing, radii, type sizes, icon sizes, and gaps allcalc()off it — change density and the whole UI rescales proportionally. Size off the tokens, not raw px. - Two density modes. Compact (
--th-font-ui→ IBM Plex Mono) and Normal (--th-font-ui→ Inter). Read--th-font-uiand--th-text-*, never a font-family or px literal, and your widget follows the mode. - Accent theming. The whole
--th-accent-*ramp is regenerated at runtime from a single hex (default#faca08). Anything that tracks the accent (--th-border-focus,--th-text-link, the panel border) re-skins automatically.--th-accent-textflips its foreground so text stays legible even on a dark custom accent. - Override via the cascade only. Themes override tokens on
:root; components read them with fallbacks. Override tokens, never component internals. Custom widgets get a live--th-*snapshot injected into their sandbox.
Token catalog
Section titled “Token catalog”Backgrounds / surfaces
Section titled “Backgrounds / surfaces”--th-bg-primary · --th-bg-secondary · --th-bg-tertiary · --th-bg-surface ·
--th-bg-surface-elevated · --th-bg-input · --th-bg-overlay · --th-bg-hover ·
--th-bg-active. Elevation on dark = a lighter surface + hairline, not a heavy shadow.
--th-text-primary · --th-text-secondary · --th-text-muted · --th-text-disabled ·
--th-text-faint (large/meta only — fails body-copy contrast) · --th-text-inverse ·
--th-text-link · --th-text-link-hover.
Borders
Section titled “Borders”--th-border-primary · --th-border-secondary · --th-border-strong · --th-border-focus
(tracks the accent).
Accent ramp (TeamHUD yellow — primary actions)
Section titled “Accent ramp (TeamHUD yellow — primary actions)”Five roles per hue: --th-accent-subtle (~12% tint) · --th-accent-border (~25%) ·
--th-accent-bright (400, text-safe on dark) · --th-accent (500, icons/accents) ·
--th-accent-fill (600, filled controls) · --th-accent-hover · --th-accent-active ·
--th-accent-muted · --th-accent-text (foreground on a fill) · --th-accent-glow.
A purple ramp mirrors it (--th-accent-purple-*) for brand/selection moments.
Semantic
Section titled “Semantic”success / warning / danger each expose -subtle · -border · -bright/-soft · solid ·
-fill · -muted (e.g. --th-success-fill, --th-danger-muted). --th-info aliases the
accent (the old cyan is retired).
Categorical identity
Section titled “Categorical identity”--th-cat-blue · -violet · -pink · -amber · -emerald · -slate — for
avatars/charts. Assign by hash of id, and never as the only differentiator (pair with
initials/labels).
Spacing
Section titled “Spacing”--th-space-0 · -1 · -2 · -3 · -4 · -5 · -6 · -8 · -10 · -12 · -16 (all multiples of
--th-u; -4 == 1×unit).
--th-radius-none · -sm · -md · -lg · -xl · -full, plus --th-control-radius (standard for
buttons/badges/chips/tabs). Use -full only for genuinely circular elements.
Typography
Section titled “Typography”- Families:
--th-font-sans(Inter) ·--th-font-mono(IBM Plex Mono) ·--th-font-ui(the one families token you consume — switches by density). - Sizes:
--th-text-2xs · -xs · -sm · -md · -base · -lg · -xl · -2xl · -3xl(scale with the density unit). - Weights:
--th-font-normal · -medium · -semibold · -bold · -extrabold. - Line heights:
--th-leading-none · -tight · -snug · -normal · -relaxed.
Shadows / elevation
Section titled “Shadows / elevation”--th-shadow-sm · -md · -lg · -xl · --th-shadow-focus · --th-shadow-inset.
Z-index
Section titled “Z-index”--th-z-base · -dropdown · -sticky · -modal · -overlay · -backdrop · -widget · -panel · -popover · -toast · -fab · -tooltip · -widget-active. Never a literal z-index.
Motion
Section titled “Motion”- Durations:
--th-duration-instant · -fast · -normal · -slow · -slower · --th-duration-pop(overlay-panel entrance). All collapse to0msunderprefers-reduced-motion. - Easings:
--th-ease-default · -in · -out · -in-out · --th-ease-bounce · --th-ease-pop. - Transitions:
--th-transition-drag(the single drag-smoothing transition for overlay hosts).
Overlay panel surface (re-skins every floating panel at once)
Section titled “Overlay panel surface (re-skins every floating panel at once)”--th-overlay-panel-bg (keep alpha ≥ 0.85 when text renders) · --th-overlay-panel-border ·
--th-overlay-panel-gap · --th-overlay-panel-idle-opacity, and width tiers
--th-panel-w-sm / -md / -lg (pick one; don’t invent a width).
Panel component tokens · scrollbar · icons
Section titled “Panel component tokens · scrollbar · icons”Panel parts: --th-panel-bg · --th-panel-header-bg · --th-panel-body-padding ·
--th-panel-item-bg-hover (and siblings). Scrollbar: --th-scrollbar-thumb / -track / -width. Icon sizes: --th-icon-size-xs / -sm / -md / -lg / -xl.
Custom-widget shared classes
Section titled “Custom-widget shared classes”Custom widgets opt into a pre-themed stylesheet:
<link rel="stylesheet" href="teamhud-widget://_shared/widget.css">. Every rule uses tokens
with fallbacks, so it renders before the token push arrives.
| Class | What it is |
|---|---|
.th-section / .th-section-label | a content group + its uppercase muted caption |
.th-kv (.th-key, .th-val) | key/value row with a hairline divider; value is tabular-nums |
.th-progress (+ .th-progress-bar, .warn, .error) | labeled progress bar with percent |
.th-chip (.warn / .error / .muted) | pill / badge |
.th-btn (.primary) | button; .primary is the accent fill |
.th-footer | footer strip (top hairline, muted) |
.th-muted · .th-mono · .th-row · .th-spacer | helpers |
The same set is available as self-contained web components (no library needed):
<th-section>, <th-progress>, <th-kv-row>, <th-chip>, <th-btn>, <th-widget-footer>.
Plugin-widget components (th-*)
Section titled “Plugin-widget components (th-*)”Plugin widgets run with full access to the @teamhud/ui-core component library — reach for
these before hand-rolling. Common ones:
- Chrome:
th-hub-panel(the overlay pop-out panel),th-hub-panel-header,th-panel,th-card,th-modal,th-window-shell. - Controls:
th-button,th-icon-button,th-input,th-textarea,th-select,th-checkbox,th-toggle,th-slider,th-color-picker. - Display:
th-badge,th-chip,th-tabs,th-section-header,th-toast,th-tooltip,th-context-menu,th-empty-state,th-spinner,th-scroll-area,th-icon.
The rules
Section titled “The rules”- Tokens, not literals for color, spacing, radius, type, z-index, and motion. Raw color
literals only for third-party brand colors (e.g. Slack
#4a154b). - Unit-derived sizes over pixels. Size off
--th-u/--th-space-*/--th-text-*. Persist widget positions as viewport fractions, never pixels. - One font:
font-family: var(--th-font-ui)— neverInteror'IBM Plex Mono'. - One palette. No new gradient brand moments;
--th-brand-*names are deprecated aliases of the accent — build on--th-accent-*. - Ramp discipline. White text only on the
-fill(600) step with--th-accent-text— never white on a 500 step (fails WCAG AA). Hover/active = one ramp step down, neverfilter: brightness(). - Color is never the only signal (WCAG 1.4.1). Pair status/error/presence color with an icon, shape, or label.
- Overlay contrast is measured against the panel surface, not the desktop — keep
--th-overlay-panel-bgalpha ≥ ~0.85 when text renders. - Don’t hardcode motion.
--th-duration-pop+--th-ease-popfor pop,--th-transition-dragfor drag. No inlinecubic-bezier(...)/msliterals; don’t pin overlay opacity withanimation-fill-mode: forwards/both. - Hub icons are inline SVG,
currentColor-driven —viewBoxonly (no rootwidth/height), and nostroke-widthattributes (a user slider tunes weight). Custom-widgetwidget.jsoniconmust be an.svg. - Use the components; don’t hand-roll a button, panel, or menu. Override tokens, never component internals.
- Overlay widgets extend the shared hub machinery (
OverlayHubWidget/th-hub-panel) — don’t write your own drag, snapping, or placement. Panels open toward viewport center; only one panel per widget is open at a time.