Skip to content

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.

  • One base unit scales everything. --th-u is a user-chosen pixel value (default 10px Compact / 14px Normal). Spacing, radii, type sizes, icon sizes, and gaps all calc() 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-ui and --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-text flips 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.

--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.

--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.

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).

--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).

--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.

  • 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.

--th-shadow-sm · -md · -lg · -xl · --th-shadow-focus · --th-shadow-inset.

--th-z-base · -dropdown · -sticky · -modal · -overlay · -backdrop · -widget · -panel · -popover · -toast · -fab · -tooltip · -widget-active. Never a literal z-index.

  • Durations: --th-duration-instant · -fast · -normal · -slow · -slower · --th-duration-pop (overlay-panel entrance). All collapse to 0ms under prefers-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 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.

ClassWhat it is
.th-section / .th-section-labela 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-footerfooter strip (top hairline, muted)
.th-muted · .th-mono · .th-row · .th-spacerhelpers

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 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.
  1. 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).
  2. Unit-derived sizes over pixels. Size off --th-u / --th-space-* / --th-text-*. Persist widget positions as viewport fractions, never pixels.
  3. One font: font-family: var(--th-font-ui) — never Inter or 'IBM Plex Mono'.
  4. One palette. No new gradient brand moments; --th-brand-* names are deprecated aliases of the accent — build on --th-accent-*.
  5. 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, never filter: brightness().
  6. Color is never the only signal (WCAG 1.4.1). Pair status/error/presence color with an icon, shape, or label.
  7. Overlay contrast is measured against the panel surface, not the desktop — keep --th-overlay-panel-bg alpha ≥ ~0.85 when text renders.
  8. Don’t hardcode motion. --th-duration-pop + --th-ease-pop for pop, --th-transition-drag for drag. No inline cubic-bezier(...) / ms literals; don’t pin overlay opacity with animation-fill-mode: forwards/both.
  9. Hub icons are inline SVG, currentColor-driven — viewBox only (no root width/height), and no stroke-width attributes (a user slider tunes weight). Custom-widget widget.json icon must be an .svg.
  10. Use the components; don’t hand-roll a button, panel, or menu. Override tokens, never component internals.
  11. 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.