--- title: Colors description: How the token layer is built, and how to rebrand it. type: reference related: - /en/docs/packages/design-system/dark-mode - /en/docs/packages/design-system/typography --- # Colors This is Tailwind v4, configured CSS-first. There is no `tailwind.config.ts` and no shared config package — everything lives in `packages/design-system/styles/globals.css`, which each app imports: ```css title="apps/app/app/styles.css" @import "tailwindcss"; @import "@kreogen/design-system/styles/globals.css"; ``` There is no `@kreogen/tailwind-config` package, and no JavaScript object you can import theme values from. In v4 the theme *is* CSS custom properties. Read them with `getComputedStyle` if you genuinely need a value at runtime, but prefer passing a `var(--color-…)` through to whatever needs it. ## Three layers The file is built in layers, and knowing which one you are editing is most of the battle. **1. `@theme` — the brand ramp.** An eleven-step scale, `--color-brand-50` through `--color-brand-950`. Because it is declared in `@theme`, Tailwind generates real utilities from it: `bg-brand-500`, `text-brand-700`, `border-brand-200`. **2. `:root` and `.dark` — semantic values.** `--background`, `--foreground`, `--primary`, `--muted`, `--destructive` and friends, defined twice: once for light, once inside `.dark`. Branded ones point at a step in the ramp rather than restating a colour. **3. `@theme inline` — the bridge.** Maps each semantic variable to the `--color-*` name Tailwind builds utilities from: ```css @theme inline { --color-background: var(--background); --color-primary: var(--primary); } ``` `inline` is what makes this work. Without it Tailwind resolves the variable once, at build time, and the light value would be baked into the utility — so `.dark` would change the custom property and nothing on screen. A token missing from this layer produces **no CSS at all**, silently. That is how `text-destructive-foreground` came to be a class that did nothing: the value existed in `:root`, the mapping did not. ## Usage ```tsx title="component.tsx" export const MyComponent = () => (

Using semantic tokens.

); ``` Prefer the semantic token over the ramp step. `bg-primary` follows a rebrand and adapts to dark mode; `bg-brand-700` does neither. Reach for the ramp when you genuinely mean "this specific step" — a chart series, a gradient stop. ## Rebranding Edit the eleven lines of the `@theme` brand ramp. Every branded token points at a step in it, so that is the whole surface — no component changes, no find-and-replace. Two things are worth knowing before you generate a ramp: **Colours are in oklch.** Perceptual lightness is the first coordinate, so interpolating between steps produces an even-looking scale rather than one that bunches in the middle the way hex interpolation does. **Chroma has a ceiling.** Push it too far at the light end and the colour falls outside sRGB; browsers clip it, and they clip it to a *different hue*, so step 200 comes out visibly off-brand. Taper chroma toward both ends rather than holding it constant. Check contrast afterwards. Whichever step you use for `--primary` needs to work both as a fill under white text and as link text on the background — those are different ratios, and a step that passes one can fail the other. Neutrals are deliberately left at zero chroma. A tinted grey fights the brand hue across large flat areas like tables and borders, where a shift of 0.005 is visible. ## Everything inherits these tokens Including the authentication screens. Sign-in, sign-up, the organization switcher and the members table are built from the same components as the rest of the application, so a rebrand reaches them with no extra configuration. That is a deliberate consequence of self-hosting auth: a hosted widget can only be themed as far as its vendor's theming API allows, which never quite matches, and the seam is always visible. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt)