Lucentive Labs
Lucentive Labs Docs
Core concepts

Theming

The --loupe-* semantic token contract, three ways to bring your own brand, and the Tailwind bridge.

Loupe ships a calm default skin, but it is built to wear any brand. Theming is one idea: a small set of semantic CSS variables — the --loupe-* contract — that every part reads from. Re-skinning Loupe is setting those variables.

The demos throughout these docs are themed with the Night Atlas tokens; the IAS demo in recipes wears signal cyan on ink. Same machinery, different tokens.

The --loupe-* token contract

You author tokens as kebab-case keys without the --loupe- prefix; core adds the prefix and sorts them deterministically. Authoring color-primary sets --loupe-color-primary.

theme: {
  "color-primary": "#2fd4c4",
  "radius-md": "8px",
  "font-sans": "Bricolage Grotesque, system-ui, sans-serif",
}

These are the tokens core defines (DEFAULT_TOKENS). Any you omit fall back to the default value.

Color

color-bgcolor-surfacecolor-fgcolor-fg-mutedcolor-primarycolor-ringcolor-signalcolor-danger
TokenRole
color-bgthe page ground
color-surfaceraised panels, tiles
color-fg / color-fg-mutedprimary / secondary text
color-primary / color-primary-fgthe load-bearing accent + text on it
color-borderhairlines and tile borders
color-ringfocus rings
color-signalthe "locked / recommended" signal
color-dangerdestructive / error

Radii, space, type, motion

GroupTokens
Radiiradius-sm, radius-md, radius-lg
Spacespace-1space-6
Typefont-sans, font-serif, font-mono
Motionease-out, duration-slow
Shadowshadow-md

The values may be any valid CSS — hex, oklch(), or even a var(--your-app-token) reference, which is how you bridge an existing design system (see below).

Three ways to bring your own brand

The same contract, applied at three different layers. Pick the one that matches where your brand lives.

Bake the brand into the config. Set theme on the Config. It travels with the decision-lock — the generated artifact and every renderer pick it up. This is the right default for a portable artifact.

loupe.config.ts
export const config: Config = {
  version: 1,
  title: "Northwind · Brand System",
  theme: {
    "color-bg": "#14110e",
    "color-surface": "#1d1813",
    "color-fg": "#f7efe3",
    "color-primary": "#f6a13c",
    "color-ring": "#f6a13c",
    "font-sans": "Manrope, ui-sans-serif, system-ui, sans-serif",
  },
  groups: [/* ... */],
};

Override at the render site. The React <Loupe theme={…} /> prop takes precedence over config.theme, so one config can be re-skinned per surface. The vanilla renderer has the same precedence via applyTheme(el, tokens).

React
import { Loupe } from "@lucentive-labs/loupe-react";

<Loupe config={config} theme={{ "color-primary": "#54deec" }} />;
Vanilla
import { mount, applyTheme } from "@lucentive-labs/loupe-dom";

const instance = mount(el, config);     // applies config.theme by default
applyTheme(el, { "color-primary": "#54deec" }); // override after mount

Set the variables in your own stylesheet. Because the contract is plain CSS variables, you can override them with any selector — handy for light/dark or for matching a parent context without touching the config.

your-app.css
.brand-northwind .loupe {
  --loupe-color-primary: #f6a13c;
  --loupe-color-bg: #14110e;
  --loupe-radius-md: 10px;
}

@media (prefers-color-scheme: dark) {
  .loupe {
    --loupe-color-bg: oklch(0.16 0.018 182);
    --loupe-color-fg: oklch(0.93 0.008 182);
  }
}

Precedence, lowest to highest: DEFAULT_TOKENSconfig.theme → the theme prop / applyTheme → any CSS rule you write with higher specificity. They merge, so partial overrides are fine.

The Tailwind bridge

If your app's design system already lives in Tailwind v4 @theme tokens, do not duplicate the values — point Loupe's tokens at yours. The token value can be a var() reference, so the bridge is one mapping:

loupe.config.ts — reference your Tailwind tokens
theme: {
  "color-bg": "var(--color-bg)",
  "color-surface": "var(--color-surface)",
  "color-fg": "var(--color-ink)",
  "color-primary": "var(--color-accent)",
  "color-ring": "var(--color-accent-ring)",
  "font-sans": "var(--font-sans)",
  "font-mono": "var(--font-mono)",
}

That is exactly how this site themes its embeds: the Night Atlas Tailwind tokens (--color-accent, --font-bricolage) flow straight into the --loupe-* contract, so the picker and the page share one brand with no second source of truth.

Helpers

Core exposes the token machinery if you need it directly:

FunctionReturns
tokensToCssVars(tokens?)a { "--loupe-*": value } object (merged with defaults, sorted)
tokensToCssText(tokens?, selector?)a ready-to-inline :root { … } CSS string
DEFAULT_TOKENSthe full default token map
import { tokensToCssText } from "@lucentive-labs/loupe-core";

const css = tokensToCssText({ "color-primary": "#54deec" }, ".loupe");
// → ".loupe {\n  --loupe-color-bg: …;\n  --loupe-color-primary: #54deec;\n  …\n}"