Colors
Koala's color system is three layers deep: a literal value, the CSS variable that holds it, and the semantic token a component writes. Components only ever reach for the top layer.
Three layers#
It helps to keep three things apart. A literal value is a raw, theme-blind color (#F84416, oklch(1 0 0)). A CSS variable (--background, --primary) holds one literal per theme. A semantic token is the utility a component writes (bg-background, text-foreground). It resolves to the variable, never to a literal. Change a theme by swapping the variables; the literals move, the tokens stay put.
/* 1: literal value (raw, theme-blind) */
oklch(0.205 0 0)
/* 2: CSS variable, one literal per theme */
:root { --primary: oklch(0.205 0 0); } /* light */
.dark { --primary: oklch(0.922 0 0); } /* dark */
/* 3: semantic token, what a component writes */
@theme inline { --color-primary: var(--primary); }
.button { background: var(--color-primary); } /* bg-primary */The swatches below document the token and variable layers, the part you build with. The literal colors at the bottom are the raw escape hatch. For changing the variables, see Theming.
Surfaces#
Each swatch shows the token a component writes and, below it, the CSS variable it resolves to.
Backgrounds & surfaces
bg-background
--background
App background
bg-canvas
--canvas
App shell frame: black in light themes, the ground in dark ones
bg-card
--card
Cards, raised panels
bg-popover
--popover
Popovers, menus
bg-muted
--muted
Subtle fills
bg-secondary
--secondary
Secondary fills
bg-accent
--accent
Hover / active accent
Content#
Text & foreground
text-foreground
--foreground
Primary text
text-body
--body
Reading copy: ledes, quotes, answers
text-muted-foreground
--muted-foreground
Meta: captions, roles, dates
text-card-foreground
--card-foreground
Text on cards
Inline text links use one role, text-link, derived from --brand so a link carries the active accent. It stays its own role because it is tuned for text where --brand is tuned for fills: the raw accent only reaches 3.6:1 as text on white, so the light theme darkens it to clear the 4.5:1 AA floor while the dark themes use it straight. Keep it distinct from text-info, which is a status hue and never a link. Links carry a hairline underline at rest and turn both the text and the underline text-link on hover. Do not hand-roll that treatment: use <Link variant="prose">.
Links
text-link
--link
Inline text links (the accent, darkened for text on light)
Brand & state#
Actions
bg-primary
--primary
Primary actions
bg-secondary
--secondary
Secondary actions
bg-brand
--brand
Accent, set via data-accent
text-brand-foreground
--brand-foreground
Ink on a brand fill (white, dark on lime)
bg-destructive
--destructive
Destructive / error
Status#
Status roles read at full strength for text and icons; soft badges derive their tint from the same role via opacity (bg-success/10 + text-success).
Feedback roles
bg-success
--success
Success / positive
bg-warning
--warning
Warning / caution
bg-info
--info
Informational
bg-destructive
--destructive
Error / danger
Categorical hues#
A small, fixed set of distinct hues for labels, tags and category badges. Use these when items need to be told apart, not ranked.
Label & tag hues
bg-purple
--purple
bg-pink
--pink
bg-teal
--teal
bg-orange
--orange
Lines & focus#
Borders & rings
border-border
--border
Default borders
border-input
--input
Form field borders
ring-ring
--ring
Focus rings
Literal colors#
The bottom layer: raw values that do not respond to theming. Koala authors these in oklch for a wider, more even gamut, but any color-mix()-friendly literal works, including plain hex. Reach for a literal only when a color must stay fixed across every theme (a logo, a chart series, a brand mark). For anything that should restyle, use a semantic token above.
The brand accent (default)
#F84416
Hex: the default Koala accent
oklch(0.648 0.222 34.2)
The same color, as authored
Tailwind v4’s entire oklch palette ships out of the box: 22 families × 11 shades, each a bg-* / text-* / border-* utility (bg-blue-500, text-rose-600, …). Every chip below is a fixed literal; hover one to read its family-shade name and oklch value.
Rule of thumb: if it should change when the theme changes, it is a token: reach for a role above. If it must stay exactly that color forever, it is a literal.