---
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 = () => (
);
```
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)