Guides

Theming and tokens

Style with the --dd-* design tokens so widgets follow every theme, read token values from JavaScript, and react to theme changes at runtime.

Every color, size, font, radius, and duration in DeskDash flows from the --dd-* design tokens. When the user switches themes, the host rewrites the token values on your document root and the whole desktop restyles in one paint, widgets included, with no reloads. Your side of that bargain is simple: style with the tokens, never with literal values. A hardcoded #3b82f6 looks fine on your wallpaper today and wrong on every theme you did not test.

The complete token list with defaults is the token reference. This page is about using them well.

Tokens from CSS

Plain var() everywhere a visual value appears:

.forecast-row {
  gap: var(--dd-space-2);
  border-bottom: 1px solid var(--dd-border);
  color: var(--dd-text-muted);
}

For text, you rarely even write CSS: <dd-text variant="title">, size="xl", weight="semibold" and friends map onto the type tokens for you. The one sanctioned escape is a truly special size, like a hero readout scaled to the widget box, where a plain font-size on that element (via an id or class selector) overrides the variant default:

#time {
  font-size: min(19vw, 52vh);  /* the Clock's hero digits */
}

Viewport units are your own widget box, so this still scales with the instance; it just is not a theme concern.

Tokens from JavaScript

CSS resolves var() for you; canvas drawing and dynamically built inline SVG cannot. For those, read the computed value with dd.ui.token and re-read it on theme changes, never caching across one:

let accent = dd.ui.token("--dd-accent", "#888888");

dd.theme.onChange(() => {
  accent = dd.ui.token("--dd-accent", "#888888");
  draw();                       // repaint with the new palette
});

The fallback is only for a token that does not resolve at all; never treat it as the real color. The Slot Machine widget's confetti works exactly this way: the burst colors are read from tokens at draw time and re-read from dd.theme.onChange, so even a canvas animation follows the theme.

You do not need any of this for plain CSS. The theme.tokens update is delivered to every widget and the SDK re-applies the variables to your document root automatically; subscribe only when you resolve token values in JS.

The one carve-out: art whose color is its identity

A cherry is red and a bell is gold in every theme. Recoloring recognizable symbol art per theme just produces shapes nobody recognizes, so a symbol set may carry its own palette: declared once as custom properties, used by nothing but the art, with everything around it fully tokenized. The Slot Machine's reel symbols are the worked example:

:root {
  --slot-cherry: #e0374b;
  --slot-leaf: #5aa84f;
  /* ...the symbol palette, used only by the symbol art */
}

The cabinet, reels, payline and type around those symbols all stay on --dd-* tokens and restyle with the theme. Keep the carve-out that narrow.

Fonts

Never write a literal font-family. Text flows through the three face tokens (--dd-font-sans, --dd-font-display, --dd-font-mono), which themes may repoint. When a widget's look depends on a specific face, pin one of the bundled fonts in the manifest instead:

"minAppVersion": "0.2.0",
"fonts": { "use": { "display": "audiowide" } }

The stylesheet keeps using var(--dd-font-display); the pin rewrites that token in your documents only. One subtlety: the pin is invisible to dd.theme.tokens() (the raw host map) but visible to dd.ui.token() (computed style), so canvas widgets read faces with dd.ui.token. Bundling your own font files is covered in the manifest reference.

Autoscale: readouts that follow the widget's size

Fixed type tokens are right for chrome, but a big readout should grow with its widget. The opt-in .dd-autoscale class scales everything inside with the container's smaller edge, tuned by two tokens:

#root {
  /* gentler than the default 36cqmin / 44px */
  --dd-autoscale-size: 6cqmin;
  --dd-autoscale-max: 20px;
}

Rules that make it behave:

  • The class must sit on an element whose size comes from outside (absolute inset, or percentage width and height). A content-sized box collapses under container sizing.
  • Size everything inside in em, inline SVG included, so it all rides the scaled font.
  • Opt a single element out with a plain font-size.

Because the knobs are tokens, a theme can retune autoscale globally and your per-widget values still layer on top.

Motion

Transitions and animations time themselves with --dd-anim-fast, --dd-anim-slow, and --dd-ease, so a theme can retime the whole desktop. Use them in your own transitions too. Two freebies and one rule:

  • The OS reduced-motion preference disables the house motion automatically; you never guard for it.
  • Keep custom keyframes to opacity and transform. Geometry-changing animations fight the input-capture tracker, which re-measures your interactive regions when layout changes.

What themes do at runtime

Knowing what actually changes helps you trust the tokens:

  • The automatic theme derives the accent family and surface and border tints from the user's current wallpaper, and follows wallpaper changes live, slideshows included. Text, status and chart colors stay at their defaults. You cannot assume any particular accent hue, ever.
  • The panel opacity slider (Settings, Themes) retunes the alpha of --dd-surface and --dd-surface-strong under any theme. --dd-surface-transparent is never touched; it is transparent by contract.

The takeaway for both: build surfaces from the surface tokens (or just use dd-panel, which does) instead of painting your own backgrounds, and all of this behavior reaches your widget for free.

The dev kit stylesheet

A scaffolded widget's CSS starts with @import "./.deskdash/dd-tokens.css";, a generated file holding every token at its default value. That import exists so the page renders sensibly in a plain browser and so your editor can autocomplete token names. Inside DeskDash it never fights the real theme: the SDK applies the live values as inline style on the document root, which outranks any stylesheet.