Saltar al contenido
Todos los artículos
design systemsthree.jstailwind

One design token, two worlds — theming the UI and a 3D scene together

How to make light/dark mode and accent colours drive both Tailwind and a Three.js scene from a single source of truth.

Mohamed Safkhan 1 min de lectura

Most portfolio sites that add 3D end up with two colour systems: one in CSS for the interface, and a second, hard-coded one inside the WebGL scene. The moment you add dark mode or let visitors pick an accent, the two drift apart.

The fix is simple: CSS custom properties are the single source of truth, and the 3D scene reads them.

1. Define tokens once

:root { --accent: #b42318; --accent-2: #c2410c; --scene-bg: #faf6f1; }
[data-theme="dark"] { --accent: #e5483a; --accent-2: #f28a2e; --scene-bg: #120b08; }

Tailwind v4 maps them to utilities with @theme inline, so bg-accent and text-fg always follow the active theme.

2. Let the scene observe the tokens

Inside React Three Fiber, read the computed values and watch the <html> element for theme changes:

const observer = new MutationObserver(() => setColors(readColors()));
observer.observe(document.documentElement, {
  attributes: true,
  attributeFilter: ["data-theme", "data-accent"],
});

3. Ease, don't snap

Instead of swapping material colours instantly, lerp them a little every frame. The whole scene fades into the new palette alongside the CSS transition — it feels deliberate rather than glitchy.

Why it matters

  • One place to re-brand. Change a hex value, and the buttons, gradients, particles and reflections all follow.
  • No drift. New components can't accidentally use an off-palette colour.
  • Drop-in compatible. Keep token names stable and you can swap entire palettes without touching components.

This site uses exactly this approach — try the appearance menu in the top bar.