Always use semantic tokens instead of raw Tailwind colors. This ensures your UI automatically adapts to light and dark mode, and that your components remain consistent across themes.
< div class = "bg-kumo-base text-kumo-default border-kumo-hairline" >
< button class = "bg-kumo-brand text-white" > Primary </ button >
< button class = "bg-kumo-control text-kumo-default" > Secondary </ button >
</ div >
{
/* Never use raw Tailwind colors */
}
< div class = "bg-white dark:bg-gray-900 text-black dark:text-white" >
< button class = "bg-blue-500" > Primary </ button >
</ div > ;
Lint rules enforce this: The no-primitive-colors rule will flag any raw Tailwind colors like bg-blue-500.
Set data-mode on a parent element to control light/dark mode. Never use Tailwind’s dark: variant — semantic tokens handle dark mode automatically via CSS light-dark().
// Set mode on html or body
< html data-mode = "light" > // Light mode
< html data-mode = "dark" > // Dark mode
// Components automatically adapt - no dark: variants needed
< div class = "bg-kumo-base text-kumo-default" />
Themes override semantic token values while preserving the same token names. Set data-theme on a parent element to apply a theme.
kumo — Default theme (no attribute needed)
fedramp — Government compliance styling
// Apply a theme to a section or the whole app
< div data-theme = "fedramp" >
{ /* All Kumo components inside use fedramp token overrides */ }
< Button > FedRAMP Styled </ Button >
</ div >
// Themes work with both light and dark mode
< html data-mode = "dark" data-theme = "fedramp" >
Themes are defined in the shared Kumo style config and generated as CSS files.
The React source package remains the canonical token generator; the Solid build
copies those generated styles so both implementations stay visually identical.
# List all tokens and their theme overrides
pnpm --filter @cloudflare/kumo codegen:themes --list
# Generate theme CSS files
pnpm --filter @cloudflare/kumo codegen:themes
# Preview changes without writing files
pnpm --filter @cloudflare/kumo codegen:themes --dry-run
# Copy the canonical generated styles into the Solid package
pnpm --filter @photon-ai/kumo-solid build
Theme config: packages/kumo/scripts/theme-generator/config.ts
Add theme overrides in the config file. Only override tokens that need to change — all other tokens inherit from the base kumo theme.
// In scripts/theme-generator/config.ts
export const THEME_CONFIG : ThemeConfig = {
color: {
"kumo-base" : {
newName: "" ,
theme: {
kumo: {
light: "var(--color-white, #fff)" ,
dark: "var(--color-black, #000)" ,
},
// Add your theme override
myTheme: {
light: "#f0f4f8" ,
dark: "#1a1f2e" ,
},
},
},
// ... other tokens
},
};
// Add to available themes
export const AVAILABLE_THEMES = [ "kumo" , "fedramp" , "myTheme" ] as const ;
Then run pnpm codegen:themes to generate the CSS.
We use semantic tokens to group colors by purpose. Use the token that matches the role of the element, not the color you want to achieve.
Semantic tokens are named by role , not by hue. A token like bg-kumo-danger communicates intent — it doesn’t imply a specific shade of red, and its exact value can change per theme or color mode without touching your component code.
Surfaces establish depth and layering in the UI. Use them in order from the outermost background inward.
Token Purpose bg-kumo-canvasThe outermost page background — sits behind everything bg-kumo-baseDefault component background bg-kumo-elevatedSlightly elevated surface, e.g. LayerCard.Secondary bg-kumo-recessedRecessed surface with a subtly darker fill, e.g. segmented Tabs background bg-kumo-tintSubtle tinted background for tables or hover states bg-kumo-contrastHigh-contrast, inverted background
Token Purpose bg-kumo-brandPrimary brand background bg-kumo-brand-hoverHover state for brand backgrounds
Each status color comes in two variants: a solid color for icons and indicators, and a -tint variant for background fills behind content (i.e. Badge or Banner).
Token Purpose bg-kumo-infoInfo indicator bg-kumo-successSuccess indicator bg-kumo-warningWarning indicator bg-kumo-dangerError/destructive indicator
Use the solid token bg-kumo-* for status dots, fill-kumo-* for icons, and border-kumo-*, ring-kumo-* for borders and rings. Banners and badges use the -tint variant with varying opacity values.
import { WarningIcon } from "~/components/icons" ;
export function StatusBannerDemo () {
return (
< div class = "flex items-center gap-2 rounded-lg bg-kumo-danger-tint/70 p-4" >
< WarningIcon weight = "fill" className = "fill-kumo-danger" />
< span class = "text-sm text-kumo-danger" > Something went wrong. </ span >
</ div >
);
}
Token Purpose text-kumo-defaultPrimary body text text-kumo-strongStronger text contrast than default for headers and important labels text-kumo-subtleMuted text for descriptions, captions, or secondary labels text-kumo-inactiveDisabled or inactive text text-kumo-placeholderPlaceholder text in inputs text-kumo-inverseText intended for use on high-contrast or inverted backgrounds text-kumo-linkLink text text-kumo-infoInfo-colored text text-kumo-successSuccess-colored text text-kumo-warningWarning-colored text text-kumo-dangerError/destructive text
Semantic text colors (i.e. text-kumo-success) are darker by default to provide better contrast and readability against tint-* backgrounds.
Token Purpose kumo-hairlineNew A border/ring color to distinguish between flat surfaces where no shadow is present (i.e. LayerCard). kumo-hairlineA thicker border/ring color that defines the edge of an elevated surface alongside a shadow.
Toggle the theme in the header to see how tokens adapt. Tokens marked as “global” are explicit opt-in classes available regardless of theme.
Colors Displaying 63 tokens
Text Colors (12) --text-color-kumo-default
Light var(--color-neutral-900, oklch(21% 0.006 285.885))
Dark var(--color-neutral-100, oklch(97% 0 0))
--text-color-kumo-inverse
Light var(--color-neutral-100, oklch(97% 0 0))
Dark var(--color-neutral-900, oklch(20.5% 0 0))
--text-color-kumo-strong
Light var(--color-neutral-950, oklch(14.5% 0 0))
Dark var(--color-neutral-50, oklch(98.5% 0 0))
--text-color-kumo-subtle
Light var(--color-neutral-500, oklch(55.6% 0 0))
Dark var(--color-neutral-400, oklch(70.8% 0 0))
--text-color-kumo-inactive
Light var(--color-neutral-300, oklch(87% 0 0))
Dark var(--color-neutral-600, oklch(43.9% 0 0))
--text-color-kumo-placeholder
Light var(--color-neutral-400, oklch(70.8% 0 0))
Dark var(--color-neutral-500, oklch(55.6% 0 0))
--text-color-kumo-link
Light var(--color-blue-800, oklch(42.4% 0.199 265.638))
Dark var(--color-blue-400, oklch(70.7% 0.165 254.624))
--text-color-kumo-info
Light var(--color-blue-800, oklch(42.4% 0.199 265.638))
Dark var(--color-blue-400, oklch(70.7% 0.165 254.624))
--text-color-kumo-success
Light var(--color-emerald-800, oklch(43.2% 0.095 166.913))
Dark var(--color-emerald-200, oklch(90.5% 0.093 164.15))
--text-color-kumo-danger
Light var(--color-red-700, oklch(50.5% 0.213 27.518))
Dark var(--color-red-400, oklch(70.4% 0.191 22.216))
--text-color-kumo-warning
Light oklch(59.7% 0.144 57.5)
Dark var(--color-orange-400, oklch(75% 0.183 55.934))
Surface, State & Theme Colors (28) --color-kumo-canvas
Light var(--color-kumo-neutral-25, oklch(98.75% 0 0))
Dark var(--color-kumo-neutral-1000, oklch(10% 0 0))
--color-kumo-elevated
Light var(--color-kumo-neutral-75, oklch(98% 0 0))
Dark var(--color-kumo-neutral-975, oklch(12% 0 0))
--color-kumo-recessed
Light var(--color-kumo-neutral-125, oklch(96% 0 0))
Dark var(--color-kumo-neutral-950, oklch(15% 0 0))
--color-kumo-base
Light var(--color-white, #fff)
Dark var(--color-kumo-neutral-925, oklch(17% 0 0))
--color-kumo-tint
Light var(--color-neutral-100, oklch(97% 0 0))
Dark var(--color-kumo-neutral-800, oklch(26.9% 0 0))
--color-kumo-contrast
Light var(--color-kumo-neutral-975, oklch(8.5% 0 0))
Dark var(--color-kumo-neutral-25, oklch(98.5% 0 0))
--color-kumo-overlay
Light var(--color-kumo-neutral-50, oklch(97.5% 0 0))
Dark var(--color-neutral-800, oklch(26.9% 0 0))
--color-kumo-control
Light var(--color-white, #fff)
Dark var(--color-neutral-900, oklch(21% 0.006 285.885))
--color-kumo-interact
Light var(--color-neutral-300, oklch(87% 0 0))
Dark var(--color-neutral-700, oklch(37.1% 0 0))
--color-kumo-fill
Light var(--color-neutral-200, oklch(92.2% 0 0))
Dark var(--color-neutral-800, oklch(26.9% 0 0))
--color-kumo-fill-hover
Light var(--color-kumo-neutral-125, oklch(96.5% 0 0))
Dark var(--color-neutral-800, oklch(37.1% 0 0))
--color-kumo-brand
Light oklch(0.5772 0.2324 260)
Dark color-mix(in oklch, oklch(0.5772 0.2324 260), black 10%)
--color-kumo-brand-hover
Light var(--color-blue-700, oklch(48.8% 0.243 264.376))
Dark var(--color-blue-700, oklch(48.8% 0.243 264.376))
--color-kumo-line
Light oklch(14.5% 0 0 / 0.1)
Dark var(--color-kumo-neutral-750, oklch(32% 0 0))
--color-kumo-hairline
Light var(--color-kumo-neutral-150, oklch(93.5% 0 0))
Dark var(--color-neutral-800, oklch(26.9% 0 0))
--color-kumo-focus
Light var(--color-kumo-neutral-950, oklch(15% 0 0))
Dark var(--color-kumo-neutral-150, oklch(93.5% 0 0))
--color-kumo-shadow-edge
Light oklch(0% 0 0 / 0.12)
Dark oklch(100% 0 0 / 0.1)
--color-kumo-shadow-drop
Light oklch(0% 0 0 / 0.08)
--color-kumo-tip-shadow
Light var(--color-gray-200, oklch(92.8% 0.006 264.531))
--color-kumo-tip-stroke
Dark var(--color-neutral-800, oklch(26.9% 0 0))
--color-kumo-info-tint
Light oklch(93.2% 0.032 255.6 / 0.45)
Dark oklch(38.0% 0.145 265.5 / 0.22)
--color-kumo-info
Light var(--color-blue-500, oklch(68.5% 0.169 237.323))
Dark var(--color-blue-500, oklch(68.5% 0.169 237.323))
--color-kumo-warning-tint
Light oklch(93.1% 0.107 94.6 / 0.20)
Dark oklch(35.3% 0.079 65.0 / 0.37)
--color-kumo-warning
Light oklch(73.9% 0.177 58.2)
Dark oklch(64.5% 0.168 50.0)
--color-kumo-danger-tint
Light oklch(93.6% 0.032 17.7 / 0.42)
Dark oklch(42.9% 0.176 28.7 / 0.17)
--color-kumo-danger
Light var(--color-red-500, oklch(63.7% 0.237 25.331))
Dark var(--color-red-600, oklch(57.7% 0.245 27.325))
--color-kumo-success-tint
Light oklch(96.2% 0.043 156.7 / 0.57)
Dark oklch(39.3% 0.096 152.3 / 0.20)
--color-kumo-success
Light var(--color-emerald-600, oklch(59.6% 0.145 163.225))
Dark var(--color-emerald-400, oklch(76.5% 0.177 163.223))
Component Colors (23) Badge (12) --text-color-kumo-badge-orange-subtle
Light var(--color-orange-800, oklch(47% 0.157 37.304))
Dark var(--color-orange-200, oklch(90.1% 0.076 70.697))
--text-color-kumo-badge-teal-subtle
Light var(--color-teal-800, oklch(43.7% 0.078 188.216))
Dark var(--color-teal-200, oklch(91% 0.096 180.426))
--text-color-kumo-badge-neutral-subtle
Light var(--color-neutral-800, oklch(26.9% 0 0))
Dark var(--color-neutral-200, oklch(92.2% 0 0))
--text-color-kumo-badge-inverted
Light var(--color-white, #fff)
Dark var(--color-black, #000)
--color-kumo-badge-red
Light var(--color-red-600, oklch(57.7% 0.245 27.325))
Dark var(--color-red-700, oklch(50.5% 0.213 27.518))
--color-kumo-badge-green
Light var(--color-emerald-600, oklch(59.6% 0.145 163.225))
Dark var(--color-emerald-700, oklch(50.8% 0.118 165.612))
--color-kumo-badge-orange
Light var(--color-orange-650, oklch(81.5% 0.197 76))
Dark var(--color-orange-650, oklch(81.5% 0.197 76))
--color-kumo-badge-purple
Light var(--color-purple-600, oklch(55.8% 0.288 302.321))
Dark var(--color-purple-700, oklch(49.6% 0.265 301.924))
--color-kumo-badge-teal
Light var(--color-teal-650, oklch(54.9% 0.096 184.565))
Dark var(--color-teal-700, oklch(51.1% 0.096 186.391))
--color-kumo-badge-blue
Light var(--color-blue-600, oklch(54.6% 0.245 262.881))
Dark var(--color-blue-700, oklch(48.8% 0.243 264.376))
--color-kumo-badge-neutral
Light var(--color-neutral-500, oklch(55.6% 0 0))
Dark var(--color-neutral-600, oklch(43.9% 0 0))
--color-kumo-badge-inverted
Light var(--color-neutral-950, oklch(14.5% 0 0))
Dark var(--color-white, #fff)
Banner (2) --color-kumo-banner-info
Light oklch(93.2% 0.032 255.585 / 0.7)
Dark oklch(37.9% 0.146 265.522 / 0.5)
--color-kumo-banner-warning
Light var(--color-yellow-100, oklch(97.3% 0.071 103.193))
Dark oklch(55.4% 0.135 66.442 / 0.5)
Switch (9) --color-kumo-switch-track
Light var(--color-neutral-200, oklch(92.2% 0 0))
Dark var(--color-neutral-700, oklch(37.1% 0 0))
--color-kumo-switch-track-checked
Light var(--color-blue-500, oklch(62.3% 0.214 259.815))
Dark var(--color-blue-600, oklch(54.6% 0.245 262.881))
--color-kumo-switch-ring
Light var(--color-neutral-300, oklch(87% 0 0))
Dark var(--color-neutral-600, oklch(43.9% 0 0))
--color-kumo-switch-ring-checked
Light var(--color-blue-600, oklch(54.6% 0.245 262.881))
Dark var(--color-blue-500, oklch(62.3% 0.214 259.815))
--color-kumo-switch-neutral-track
Dark var(--color-kumo-neutral-925, oklch(17% 0 0))
--color-kumo-switch-neutral-track-checked
Light var(--color-neutral-500, oklch(55.6% 0 0))
Dark var(--color-kumo-neutral-925, oklch(17% 0 0))
--color-kumo-switch-neutral-ring-checked
Light var(--color-neutral-600, oklch(43.9% 0 0))
Dark var(--color-neutral-700, oklch(37.1% 0 0))
--color-kumo-switch-thumb-checked
Light var(--color-white, #fff)
Dark var(--color-blue-300, oklch(80.9% 0.105 251.813))
--color-kumo-switch-neutral-thumb-checked
Light var(--color-white, #fff)
Dark var(--color-neutral-400, oklch(70.8% 0 0))