surface | #ffffff | oklch(21% 0.006 285.885) | The page card — what content sits on. |
surface-sunken | oklch(96.7% 0.001 286.375) | oklch(14.1% 0.005 285.823) | The ground behind the card and sidebar. |
surface-subtle | oklch(98.5% 0 0) | oklch(27.4% 0.006 286.033) | Table heads and row hover. 259 uses. |
surface-inverse | oklch(14.1% 0.005 285.823) | oklch(98.5% 0 0) | Primary buttons and the mobile FAB. Flips in dark — light-on-dark becomes dark-on-light. |
surface-inverse-hover | oklch(27.4% 0.006 286.033) | oklch(92% 0.004 286.32) | Primary button hover. Light lifts zinc-950 to zinc-800; dark drops zinc-50 to zinc-200 — both move AWAY from the page, which is what reads as a press-ready state. |
on-inverse | #ffffff | oklch(14.1% 0.005 285.823) | Text and icons on surface-inverse. |
scrim | oklch(0% 0 0 / 0.4) | oklch(0% 0 0 / 0.6) | The dimmed layer behind a modal, drawer or sheet. Stays dark in BOTH themes — deriving it from `fg` would make it white in dark, which lights the page up instead of pushing it back. |
border | oklch(14.1% 0.005 285.823 / 0.1) | oklch(100% 0 0 / 0.1) | The default hairline. zinc-950 at 10% (white at 10% in dark) rather than a solid step, so it reads correctly over any surface. 212 uses. |
divider | oklch(14.1% 0.005 285.823 / 0.05) | oklch(100% 0 0 / 0.06) | A rule BETWEEN items inside a container, as opposed to the container's own edge. Deliberately lighter than `border` (zinc-950 at 5% vs 10%) — a divider that matches the outer border makes a panel read as a table. |
border-strong | oklch(92% 0.004 286.32) | oklch(37% 0.013 285.805) | Cards that must read as raised — stat tiles, the active nav item. |
fg | oklch(14.1% 0.005 285.823) | #ffffff | Headings and emphasis. 748 uses. |
fg-body | oklch(37% 0.013 285.805) | oklch(87.1% 0.006 286.286) | Running copy in rows and cells. 309 uses. |
fg-muted | oklch(55.2% 0.016 285.938) | oklch(70.5% 0.015 286.067) | Labels, meta, subtitles. 850 uses — the single most-used class in the app. |
fg-subtle | oklch(70.5% 0.015 286.067) | oklch(55.2% 0.016 285.938) | Resting icon colour and timestamps. 438 uses. |
accent | oklch(54.6% 0.245 262.881) | oklch(70.7% 0.165 254.624) | Links and informational emphasis. Settles the blue-409 vs indigo-438 split. |
accent-hover | oklch(48.8% 0.243 264.376) | oklch(80.9% 0.105 251.813) | |
on-accent | #ffffff | oklch(14.1% 0.005 285.823) | |
focus-ring | oklch(62.3% 0.214 259.815) | oklch(62.3% 0.214 259.815) | Kept identical in both themes — it must read against every surface, and blue-500 already was the Catalyst focus colour. |
success | oklch(59.6% 0.145 163.225) | oklch(76.5% 0.177 163.223) | |
success-tint | oklch(95% 0.052 163.051) | oklch(76.5% 0.177 163.223 / 0.15) | Badge and alert background. Light uses the fixed 100 step; dark uses 15% of the base over the surface, so one rule covers both themes without a second hand-tuned ramp. |
success-wash | oklch(97.9% 0.021 166.113) | oklch(76.5% 0.177 163.223 / 0.08) | Large-surface fill for the success tone — alert strips, banners. Deliberately lighter than `success-tint`: the same hue needs more saturation to register in a badge than it does across a whole alert, so a tint used at panel size reads as shouting. |
on-success-tint | oklch(50.8% 0.118 165.612) | oklch(84.5% 0.143 164.978) | |
warning | oklch(66.6% 0.179 58.318) | oklch(82.8% 0.189 84.429) | |
warning-tint | oklch(96.2% 0.059 95.617) | oklch(82.8% 0.189 84.429 / 0.15) | |
warning-wash | oklch(98.7% 0.022 95.277) | oklch(82.8% 0.189 84.429 / 0.08) | Large-surface fill for the warning tone — alert strips, banners. Deliberately lighter than `warning-tint`: the same hue needs more saturation to register in a badge than it does across a whole alert, so a tint used at panel size reads as shouting. |
on-warning-tint | oklch(55.5% 0.163 48.998) | oklch(87.9% 0.169 91.605) | |
danger | oklch(57.7% 0.245 27.325) | oklch(70.4% 0.191 22.216) | |
danger-hover | oklch(50.5% 0.213 27.518) | oklch(80.8% 0.114 19.571) | |
danger-tint | oklch(93.6% 0.032 17.717) | oklch(70.4% 0.191 22.216 / 0.15) | |
danger-wash | oklch(97.1% 0.013 17.38) | oklch(70.4% 0.191 22.216 / 0.08) | Large-surface fill for the danger tone — alert strips, banners. Deliberately lighter than `danger-tint`: the same hue needs more saturation to register in a badge than it does across a whole alert, so a tint used at panel size reads as shouting. |
on-danger-tint | oklch(50.5% 0.213 27.518) | oklch(80.8% 0.114 19.571) | |
on-danger | #ffffff | oklch(14.1% 0.005 285.823) | Text on a filled danger surface. Dark flips to a near-black because `danger` lightens to red-400 there — white on red-400 is 2.87:1 and fails AA. Mirrors how on-accent is handled. |
accent-tint | oklch(93.2% 0.032 255.585) | oklch(70.7% 0.165 254.624 / 0.15) | |
accent-wash | oklch(97% 0.014 254.604) | oklch(70.7% 0.165 254.624 / 0.08) | Large-surface fill for the accent tone — alert strips, banners. Deliberately lighter than `accent-tint`: the same hue needs more saturation to register in a badge than it does across a whole alert, so a tint used at panel size reads as shouting. |
on-accent-tint | oklch(48.8% 0.243 264.376) | oklch(80.9% 0.105 251.813) | |
neutral-tint | oklch(92% 0.004 286.32) | oklch(100% 0 0 / 0.1) | The default badge, for states with no semantic charge. |
on-neutral-tint | oklch(37% 0.013 285.805) | oklch(87.1% 0.006 286.286) | |