Every material exposes color (and sometimes intensity) props that accept any valid CSS color. The production pattern is to pass var(--…) references from your theme — the same variables your buttons, cards, and text already use.
Token binding
Prefer semantic tokens over raw hex. Example for a mesh that takes a color array:
import { AuroraMesh } from "@/components/ui/aurora-mesh"
export function BrandedHero() {
return (
<AuroraMesh
aria-hidden
className="absolute inset-0"
colors={[
"var(--brand-surface)",
"var(--brand-accent)",
"var(--brand-highlight)",
"var(--brand-ink)",
]}
/>
)
}- Map material props 1:1 to your token names in one place (a thin wrapper component) so product screens never hardcode Frameline demos.
- Keep fallback gradients in sync — materials ship
fallbackColorsfor reduced-motion and pre-mount states; override them with the same token set when you customize. - Opacity and blend props should stay unitless or use theme-owned spacing scales — avoid one-off magic numbers per page.
CSS variables
Define tokens on :root and a dark selector your app already uses (.dark, [data-theme="dark"], etc.):
:root {
--brand-surface: oklch(0.97 0.02 220);
--brand-accent: oklch(0.62 0.2 264);
--brand-highlight: oklch(0.72 0.18 330);
--brand-ink: oklch(0.22 0.02 264);
}
.dark {
--brand-surface: oklch(0.22 0.03 264);
--brand-accent: oklch(0.7 0.16 264);
--brand-highlight: oklch(0.75 0.14 330);
--brand-ink: oklch(0.95 0.01 220);
}Because the material reads live CSS variables, flipping the theme class (or prefers-color-scheme if that drives your tokens) updates the surface without remounting or forking the component.
Dark mode without a fork
- Do not ship a second “dark” material. One component + token pairs is the contract.
- Preview both themes on the material detail page before you install — contrast for overlaid text can change dramatically.
- If a WebGL material samples colors at mount, ensure your theme class is applied before first paint (same rule as any tokenized UI).
Related: contrast over materials · theme colors look wrong.