Skip to content

Define CSS custom properties at :root level for design tokens.

function theme(tokens: ThemeTokens, options?: ThemeOptions): void
// Since alpha.9 — exported from "@whisq/core"
interface ThemeOptions {
/** Suppress the dev-mode "theme() called twice" warning. */
silent?: boolean;
}
ParamTypeDescription
tokensThemeTokensObject of token groups and values
options.silentboolean (optional, since alpha.9)Suppress the dev-mode duplicate-call warning. Use for legitimate second calls (theme-switching). Default: false.
import { theme, sheet } from "@whisq/core";
theme({
color: {
primary: "#4386FB",
text: "#111827",
bg: "#ffffff",
},
space: { sm: "0.5rem", md: "1rem", lg: "1.5rem" },
radius: { md: "8px", lg: "12px" },
});
// Generated CSS: --color-primary: #4386FB; --space-md: 1rem; etc.
// Use in sheet():
sheet({
card: {
background: "var(--color-bg)",
padding: "var(--space-lg)",
borderRadius: "var(--radius-lg)",
},
});
  • Call once, at module scope in your src/styles.ts file. The tokens become CSS custom properties on :root and are available everywhere. Import styles.ts transitively from App.ts so the call runs on first import.

  • Duplicate calls = last-call-wins. A second theme() call replaces the first <style id="whisq-style-whisq-theme"> block entirely; the new tokens take effect immediately. This is intentional — it enables theme-switching (e.g., theme(lightTokens)theme(darkTokens) in a toggle handler) without leaking old tokens.

  • Duplicate-call dev warning (since alpha.9). Accidentally importing two styles.ts files silently wiping the first theme’s variables was the alpha.8 feedback’s top styling footgun. Alpha.9+ emits a console.warn in dev when a second theme() call would replace an existing block; use { silent: true } to acknowledge intentional second calls (theme-switching). Production doesn’t emit the warning regardless. See Duplicate-call warning below.

  • SSR-safe (since alpha.8). On the server (typeof document === "undefined"), the call is a no-op — no <style> tag is written. Client-side hydration applies the theme on mount. Previously threw ReferenceError: document is not defined.

    CSS variables won’t appear in server-rendered HTML by themselves; attach theme tokens in a <style> block in your SSR template until a dedicated server renderer ships.

theme() is last-call-wins by design — the second call replaces the first <style> block. Useful for theme-switching; a source of silent bugs when the duplicate is accidental (e.g., two styles.ts files imported transitively by different parts of the app).

Alpha.9 adds a console.warn in dev when a second call would replace an existing block. Two modes:

styles.ts
// Accidental duplicate — dev warns.
theme({ color: { primary: "#4386FB" } });
// another-styles.ts (imported from another branch of the graph)
theme({ color: { primary: "#ffffff" } }); // Dev: console.warn names the conflict.
// Intentional theme switch — opt out of the warning.
const toggleDark = () => {
theme(darkTokens, { silent: true }); // No warning.
};

Detection is DOM-based (getElementById("whisq-style-whisq-theme")). Production builds strip the entire guard via the existing NODE_ENV !== "production" pattern, so there’s no runtime cost in shipped code.

The silent: true option only suppresses the warning — it doesn’t change injection semantics. theme() remains last-call-wins regardless.

See also: /api/sheet/, Styling guide, Common Mistakes → theme variables flipped unexpectedly.

Docs current to v0.1.0-alpha.9 . All releases →