Lumeo

Theming with tweakcn / shadcn

tweakcn is a visual theme editor for the shadcn/ui ecosystem. Lumeo's color tokens are natively compatible with it: paste a shadcn / tweakcn :root / .dark export and the whole library re-skins — no adapter, no import, no renaming.

TL;DR

  • Build a theme at tweakcn, export the Tailwind v4 / CSS-variables form (OKLCH).
  • Paste its :root / .dark blocks into your app, unlayered and loaded after lumeo.css.
  • Colors, --radius, --shadow-*, --spacing and --font-sans drive Lumeo 1:1 — Tailwind v4 reads them as variables. tweakcn's --tracking-* does not map (Lumeo reads different names — see caveats), and Lumeo's own status colors have no tweakcn source. Both keep Lumeo's defaults.

Verified against the prebuilt lumeo.css + lumeo-utilities.css in a headless browser (light + dark, computed styles).

How it works

shadcn / tweakcn write unprefixed variables (--primary); Lumeo's components read the Tailwind v4 --color-* namespace. Lumeo maps the two natively at the source — every color token is defined as the shadcn variable with Lumeo's own value as the fallback (no separate adapter or import file involved):

/* lumeo.css — every color token maps to the shadcn name (built in, no import) */
:root {
    --color-primary:    var(--primary,    oklch(0.21 0.006 286));
    --color-background: var(--background, oklch(1 0 0));
    --color-border:     var(--border,     oklch(0.92 0.004 286));
    --color-ring:       var(--ring,       oklch(0.21 0.006 286));
    /* …every color token, base + dark + all palettes… */
    --radius: 0.75rem;   /* same name as shadcn — nothing to map */
}

So if you set --primary, it flows straight into --color-primary and every bg-primary / button / ring picks it up. Colors work regardless of load order because this is variable indirection, not a cascade override.

The non-color tokens are different. Lumeo declares --radius and the --shadow-* scale itself (unlayered), and --spacing / --font-* come from Tailwind's @layer theme defaults. A tweakcn export is an unlayered :root, so pasted after the Lumeo import it wins the cascade over both — which is exactly why the order matters for these.

Steps

1 — Generate the theme

At tweakcn, dial in your colors and corner radii, then Code / Export. Choose OKLCH and Tailwind v4, and copy the :root and .dark blocks.

2 — Confirm the Lumeo import, then paste after it

Your global stylesheet must import Lumeo's CSS. Paste both tweakcn blocks into the same file after that import, unlayered.

Tailwind v4

@import "./_content/Lumeo/css/lumeo.css" layer(base);

/* paste your tweakcn / shadcn export below — unlayered, so it wins */
:root { --primary: oklch(0.205 0 0); --radius: 0.625rem; /* … */ }
.dark  { --primary: oklch(0.985 0 0); /* … */ }

Without Tailwind (prebuilt CSS assets)

<link href="_content/Lumeo/css/lumeo.css" rel="stylesheet" />
<!-- tweakcn export AFTER lumeo.css so it wins the cascade -->
<style>
    :root { --primary: oklch(0.205 0 0); --radius: 0.625rem; /* tweakcn light vars */ }
    .dark { --primary: oklch(0.985 0 0); /* tweakcn dark vars  */ }
</style>

Load order matters for everything except colors. You can ignore the @import "tailwindcss", @theme inline and @layer base lines in the tweakcn export — Lumeo already provides those.

3 — Dark mode is wired automatically

Lumeo's ThemeService toggles the .dark class on <html> — exactly the selector tweakcn's .dark block targets. Light and dark switch with no extra wiring.

4 — Rebuild CSS

If you run a Tailwind build step (the Lumeo PostCSS preset or tailwindcss --watch), let it rebuild so the new custom-property values are picked up. Using the prebuilt CSS assets, a browser refresh is enough.

What carries over — and what doesn't

Token group Carries over Notes
All colors
Yes
background, foreground, card, popover, primary, secondary, muted, accent, destructive (+ foreground), border, input, ring, chart-1…5, and the full sidebar family — all map 1:1, order-independent (variable indirection).
--radius
Yes
Same name in both. Lumeo derives --radius-sm/md/lg/xl from it, so set only the master --radius (don't paste tweakcn's derived scale). Must be pasted after Lumeo to win. (Verified: 0.75rem → 1.4rem moved rounded-lg from 15px → 28px.)
--spacing
Yes
Tailwind v4 compiles Lumeo's spacing utilities to calc(var(--spacing) * n), so an unlayered --spacing pasted after Lumeo rescales every padding / margin / gap / size. (Verified: 0.25rem → 0.5rem doubled p-4 16px → 32px and gap-2 8px → 16px.)
--shadow-*
Yes
Lumeo maps the shadow-* utilities to var(--shadow-*) (overriding only --tw-shadow, so focus-ring composition stays intact). A pasted --shadow-2xs--shadow-2xl drives every elevation 1:1, again pasted after Lumeo. (Verified via a custom --shadow-md.)
Fonts
Partial
--font-sans re-types the whole UI (Lumeo's body inherits it). --font-mono / --font-serif flow too, but only reach elements that use the font-mono / font-serif utilities — Lumeo components default to sans. (Verified: --font-sans and --font-mono both applied to their targets.)
Tracking (--tracking-*)
No
tweakcn exports --tracking-normal (plus a @theme inline scale), but Lumeo's utilities read --tracking-tight/tighter/wide/… by name — names the copied :root / .dark blocks never set, so tracking is untouched. Set those exact names yourself to drive it. (Verified: --tracking-normal was inert; --tracking-tight set directly worked.)
Lumeo status colors
No
tweakcn never emits info / success / warning / positive / rating (or their -light / -text variants), so they keep Lumeo's defaults. Override the --color-* directly if a recolored palette clashes.

Caveats

  • Paste order matters for everything but colors. --radius, --shadow-*, --spacing and the --font-* tokens are declared by Lumeo or by Tailwind's @layer theme, so the tweakcn block must come after the Lumeo import and stay unlayered to win the cascade. Colors are exempt (variable indirection).
  • Tracking does not carry over. A standard tweakcn copy leaves --tracking-* inert because of the name mismatch above. Set --tracking-tight etc. directly if you need it.
  • Fonts are partial. --font-sans re-types the UI; --font-mono / --font-serif only affect elements that opt into those utilities.
  • Prebuilt assets vs your own Tailwind build behave the same. Whether you load Lumeo's prebuilt lumeo-utilities.css or compile your own Tailwind v4 build over Lumeo's markup, the utilities resolve the same var(--…) tokens, so the same variables flow and the same tracking gap applies. An own build gives you one extra lever: you can map --tracking-* (or anything else) in your own @theme.

Demo: a shadcn palette driving Lumeo

This block sets the bare shadcn names (--primary / --ring) on one subtree — exactly what a pasted tweakcn export does globally — and Lumeo picks up the teal accent with no extra wiring.

shadcn accent

Driven by a bare --primary, natively.

See also