Theme tokens
Every CSS variable the widget loader honors — what it does, default value in light and dark mode, and how to override it from the script tag or at runtime.
The widget loader exposes a single contract for visual customization: a
flat set of --hw-* CSS custom properties. Every widget reads from these
variables — recolor them once and every mounted widget picks up the new
palette without touching the loader bundle.
This page lists every variable the loader writes, how it maps to the underlying token system, and how to override it at three different levels.
How overrides flow
The loader resolves theme tokens in this order, last write wins:
- Built-in defaults —
lightordarkpalette baked into the loader. data-theme-vars— JSON-encoded--hw-*map on the script tag (set once when you paste the embed).window.HatchedWidgets.remount({ themeVars })— runtime overrides pushed from your host page after the embed has mounted. See the Runtime configuration guide for details.
Any key that does not begin with --hw- is dropped by the loader's
style serializer, so you can't accidentally leak host-page CSS variables
into the shadow DOM. Pass strings only; the loader does not parse JS values.
Try it
The playground writes overrides into a mock widget styled with the same tokens as the production loader. Adjust a token to see exactly which surface it controls, then copy the script tag at the bottom into your real embed.
Copy as data-theme-vars
<script
src="https://cdn.hatched.live/widget.js"
data-embed-token="EMBED_OR_SESSION_TOKEN"
data-theme="light"
data-theme-vars='{"--hw-accent":"#FF6B4A"}'
defer
></script>Reference
Surfaces
| Variable | Light | Dark | What it controls |
|---|---|---|---|
--hw-bg | #FAF7F2 | #0D0B08 | Outer widget shell background |
--hw-bg-elevated | #FEFCF8 | #1A1814 | Elevated panels (popovers, sheets) |
--hw-surface | #FFFFFF | #2A241A | Card backgrounds inside the shell |
--hw-surface-muted | #F4EFE6 | #1A1814 | Secondary chip / neutral pill background |
--hw-overlay | rgba(13,11,8,0.48) | rgba(0,0,0,0.72) | Modal scrim |
Borders
| Variable | Light | Dark | What it controls |
|---|---|---|---|
--hw-border | #E0D7C3 | #3D3629 | Default card and shell border |
--hw-border-strong | #CFC9BE | #5A5246 | Hover/active borders |
Text
| Variable | Light | Dark | What it controls |
|---|---|---|---|
--hw-text | #1A1814 | #FEFCF8 | Default body text |
--hw-text-secondary | #5A5246 | #ECE5D6 | Sub-headings, secondary labels |
--hw-text-muted | #7E7668 | #A8A196 | Meta text, captions |
--hw-on-accent | #FFFFFF | #0D0B08 | Text drawn on top of --hw-accent |
Accent (primary brand color)
The accent ramp is what most tenants override first. Pick a brand hue for
--hw-accent, derive a darker --hw-accent-strong for hover and a tinted
--hw-accent-soft for chips/pills.
| Variable | Light | Dark | What it controls |
|---|---|---|---|
--hw-accent | #FF6B4A | #FF8562 | Primary buttons, key chips, progress fills |
--hw-accent-strong | #E84F30 | #FFA284 | Hover, focus, active states |
--hw-accent-soft | #FFE4D8 | rgba(255,133,98,0.16) | Soft fills, badge backgrounds, ghost rows |
Semantic colors
| Variable | Light | Dark | What it controls |
|---|---|---|---|
--hw-positive | #12E8A0 | #5FFFC6 | Success states, completed progress |
--hw-positive-soft | #C9FFEC | rgba(45,255,185,0.16) | Success chip backgrounds |
--hw-info | #6E7BFF | #8E99FF | Informational toasts and tags |
--hw-warn | #F5A800 | #FFC427 | Warning ribbons, "at risk" cues |
--hw-danger | #F84C5C | #FF7C87 | Destructive actions, error toasts |
--hw-yolk | #FFC427 | #FFC427 | The Hatched egg yolk (rarely overridden) |
Geometry
| Variable | Default | What it controls |
|---|---|---|
--hw-radius | 16px | Outer shell radius |
--hw-card-radius | 12px | Card / panel radius |
--hw-button-radius | 8px | Buttons, chips |
--hw-density-scale | 1 | Multiplier on vertical rhythm (try 0.85 for compact, 1.15 for relaxed) |
--hw-pad-shell | 16px | Shell padding |
--hw-pad-card | 12px | Card padding |
--hw-gap | 12px | Default flex/grid gap inside widgets |
Typography
The loader ships Geist for body copy and Gluten for the playful display family by default. Override these to inherit the host site's fonts — the loader injects the fallback chain you provide; it does not bundle additional webfonts.
| Variable | Default | What it controls |
|---|---|---|
--hw-font-body | 'Geist', 'Inter', system-ui, sans-serif | Body text |
--hw-font-display | 'Gluten', 'DynaPuff', 'Fraunces', serif | Headlines, ceremony copy |
--hw-font-mono | 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace | Code, tokens, numeric counters |
--hw-font-base | 14px | Body size |
--hw-font-title | 20px | Section headings |
--hw-font-meta | 12px | Caption text |
--hw-font-eyebrow | 10px | Uppercase eyebrows |
Other geometry primitives
| Variable | Default | What it controls |
|---|---|---|
--hw-avatar | 64px | Avatar / buddy portrait square |
--hw-tile-min | 140px | Minimum tile width in marketplace / grid widgets |
--hw-bar-h | 8px | Progress bar height |
--hw-btn-pad-y | 8px | Button vertical padding |
--hw-btn-pad-x | 16px | Button horizontal padding |
--hw-shell-max | 360px | Widest the shell will grow before clamping |
--hw-shadow | 0 1px 2px rgba(13,11,8,0.04), 0 1px 1px rgba(13,11,8,0.02) | Card shadow |
Alias tokens (you don't need to set these)
A handful of widgets reference parallel token names that resolve to the canonical tokens above. You never have to set them — override the canonical token and the alias follows automatically. They exist only so older widget styles keep working; listing them here so nothing looks "missing".
| Alias | Resolves to |
|---|---|
--hw-color-primary / --hw-color-primary-strong / --hw-color-primary-soft | --hw-accent / --hw-accent-strong / --hw-accent-soft |
--hw-text-primary / --hw-text-default | --hw-text |
--hw-border-default / --hw-border-subtle | --hw-border |
--hw-path-accent | --hw-accent (Path widget node ramp) |
--hw-radius-card / --hw-radius-button | --hw-card-radius / --hw-button-radius |
--hw-color-text-inverse / --hw-color-surface-inverse | --hw-bg / --hw-text |
Optional overrides (no loader default)
These tokens are not written by the loader's :host block, so they
have no Light/Dark default of their own. They are only read as the first
choice in a var() fallback chain — leave them unset and the listed
fallback applies; set them with data-theme-vars (or remount) to take
control of that one surface.
| Variable | Falls back to | What it controls |
|---|---|---|
--hw-bg-sunken | --hw-bg | Inset wells (progress trays, code blocks) |
--hw-shell | #FEFCF8 (literal) | Chrome layering used by the dressing animation SVG |
--hw-skeleton-base | --hw-bg-sunken, then --hw-bg | Skeleton placeholder base |
--hw-skeleton-shine | --hw-bg-elevated, then --hw-bg | Skeleton shimmer highlight |
Setting tokens from the script tag
The simplest path: pass a data-theme-vars attribute on the loader
script. The value is JSON, keys are CSS variable names, values are
strings.
<script
src="https://cdn.hatched.live/widget.js"
data-embed-token="EMBED_OR_SESSION_TOKEN"
data-theme="light"
data-theme-vars='{"--hw-accent":"#5B5BFF","--hw-accent-strong":"#3F3FFF","--hw-accent-soft":"#E4E4FF","--hw-radius":"20px"}'
defer
></script>
<div data-hatched-mount="buddy"></div>Keys without the --hw- prefix are dropped on parse — there is no way
to leak unrelated host-page variables.
Setting tokens at runtime
When the host application is an SPA and the embed has already mounted, push new tokens through the public API:
window.HatchedWidgets.remount({
themeVars: {
'--hw-accent': '#5B5BFF',
'--hw-accent-strong': '#3F3FFF',
'--hw-accent-soft': '#E4E4FF',
},
});remount() re-runs mount discovery and updates the in-memory config —
already-mounted widgets receive the new tokens via their shadow-DOM style
elements, no flicker.
Tips
- Light + dark with one accent ramp. Override
--hw-accent,--hw-accent-strong, and--hw-accent-softonce withdata-theme-varsand the loader keeps the rest of the palette in sync withdata-theme. - Densify a sidebar embed. Set
--hw-density-scale: 0.85to drop the vertical rhythm without rewriting every widget's spacing. - Match host fonts. Override
--hw-font-bodyand--hw-font-displaywith the host site's fallback chain. The loader does not load additional webfonts — the host page is responsible for serving them. - Round buttons but square cards. Tokens are independent: pick
--hw-button-radius: 9999pxfor pill buttons while keeping--hw-card-radius: 8px.
Companion docs
- Widget integration guide — how to paste the embed, choose a mode, and respond to events.
- Widget reference — every widget and
the
data-*attributes it accepts. @hatched/widgets-loader-types— IDE autocomplete forthemeVarsandwindow.HatchedWidgets.