Theming
Per-product color customization via OKLCH palette generation. Pass two hex values — get a full 11-shade palette and a CSS override ready to inject.
How To Use This Page
Theming explains how brand tokens and generated palettes flow through the component system.
Start here when you need to adapt the catalog to a product identity without forking the component code.
How The Content Is Grouped
- The top-level Theming page groups the catalog by subcategory, making it easier to choose a family of patterns before drilling into an implementation.
- Use the highlighted links below to start with the highest-signal areas of the section.
Installation
pnpm add @hilum/uiThen import the base token CSS in your globals:
/* globals.css */
@import "tailwindcss";
@import "@hilum/ui/tokens.css";Peer dependencies
react@^19 and react-dom@^19 are required.tailwindcss@^4 is required in any app that uses the token utilities.
Palette generator
Pick any two hex colors. The algorithm treats your input as the 500-level anchor and generates 11 perceptually-uniform shades via OKLCH color space interpolation.
Interactive preview
Change the colors to see the generated palette and how components adapt
Primary
Primary-500 anchor · 11 shades generated
Secondary
Secondary-500 anchor · 11 shades generated
Primary
Secondary
Live component preview
Usage
ThemeProvider (recommended)
Wrap your app root. Re-applies automatically when props change.
import { ThemeProvider } from "@hilum/ui/create-theme"
export function App() {
return (
<ThemeProvider primary="#0066FF" secondary="#FF9900">
<Router />
</ThemeProvider>
)
}applyTheme()
Imperative alternative. Useful for Electron apps that apply a theme at init before React mounts.
import { applyTheme } from "@hilum/ui/create-theme"
// Call once at app startup (browser / Electron only)
applyTheme({ primary: "#0066FF", secondary: "#FF9900" })createTheme() — pure / build-time
Returns the raw CSS string and palette. Works in Node — use it in build scripts, SSG pipelines, or to write a static CSS file per product.
import { createTheme } from "@hilum/ui/create-theme"
// Pure function — works in Node too (build scripts, SSG)
const { css, palette } = createTheme({
primary: "#0066FF", // 500-level anchor → 11 shades generated
secondary: "#FF9900",
})
// css → inject into <style> or write to a .css file
// palette.primary["50"] → "#f2f7ff" (lightest tint)
// palette.primary["500"] → "#0066ff" (your exact input)
// palette.primary["950"] → "#000723" (darkest shade)Manual CSS override
For non-React consumers or when full palette generation isn't needed. Import after tokens.css.
/* product-theme.css — loaded after tokens.css */
:root {
--color-brand-primary: #0066FF;
--color-brand-secondary: #FF9900;
--primary: #0066FF;
--primary-foreground: #ffffff;
--accent: #f2f7ff;
--accent-foreground: #003d9f;
--ring: #0066FF;
}What gets overridden
The generated CSS overrides CSS custom properties that back Tailwind v4 utilities. Unlayered :root rules beat @layer theme, so the override takes effect without rebuilding Tailwind.
CSS variable
Value
Affects
--color-brand-primaryprimary hex
bg-brand-primary, text-brand-primary, ring-brand-primary — Button, Badge, Input focus rings
--color-brand-secondarysecondary hex
bg-brand-secondary, border-brand-secondary — Badge success/warning, Button brand
--primaryprimary hex
Semantic primary token
--primary-foregroundauto (WCAG)
Text on primary surfaces
--accent / --accent-foregroundprimary-50 / primary-700
Hover tints, accent surfaces
--ringprimary hex
Focus rings on all interactive elements
--warning / --warning-foregroundsecondary-200 / ground-900
Warning banners and callouts
--color-primary-{50…950}generated palette
Product CSS: var(--color-primary-100), etc.
--color-secondary-{50…950}generated palette
Product CSS: var(--color-secondary-100), etc.
API reference
Exports
Export
Signature
Description
createTheme(config)ThemeConfig → ThemeResultPure function. Generates palettes and returns { css, palette }. Works in Node and browser.
applyTheme(config)ThemeConfig → () => voidInjects a <style> tag into document.head. Returns a cleanup function. Browser / Electron only.
<ThemeProvider>ThemeConfig + childrenReact component. Calls applyTheme in useEffect, auto-cleans up on unmount or prop change.
ThemeConfig
Prop
Type
Description
primarystringHex color. Used as the 500-level anchor for the primary palette.
secondarystringHex color. Used as the 500-level anchor for the secondary palette.
ThemeResult
Field
Type
Description
cssstringComplete CSS override string. Inject via <style> or write to a .css file.
palette.primaryRecord<string, string>Generated shades: { "50": "#f2f7ff", …, "950": "#000723" }
palette.secondaryRecord<string, string>Same structure for the secondary color.
How palettes are generated
Palette generation uses the OKLCH color space — a perceptually uniform model where equal numerical steps produce equal perceived changes. No external dependency; the conversion math is inlined (~100 lines).
- 1.
Your hex input is parsed and converted to OKLCH: (L, C, H)
- 2.
The input is anchored at shade-500. Shades 50–400 interpolate L toward 0.975 (near-white) with chroma fading 80%. Shades 600–950 interpolate L toward 0.145 (near-black) with chroma fading 40%.
- 3.
Each generated OKLCH color is clamped to the sRGB gamut via binary search on chroma — ensuring every shade is a valid, displayable color.
- 4.
Foreground colors (primaryForeground) are chosen using WCAG relative luminance with a 0.179 threshold — the crossover point where white and black have equal contrast.