color theory

Two Layer Token System

A two layer token system splits your color decisions into primitives and semantics so you can run true parallel palettes instead of cheap inversions. Primitives are the raw OKLCH values. Think oklch(0.10 0.012 30) for background floor oklch(0.14 0.010 30) for default surface oklch(0.18 0.008 30) for cards and oklch(0.72 0.17 25) for the calibrated accent. Semantics are the names your components actually eat. Color bg color surface color surface elevated color text primary color accent. The semantics alias to the primitives. Flip the data theme attribute and the aliases redirect to the dark primitive collection. Every button card and paragraph updates without a single component edit. This structure forces deliberate elevation models desaturation curves and accent calibration because you cannot lazily invert the light values. The dark primitives must be authored from scratch for how eyes behave at low ambient light. Chroma drops 15 to 25 percent on accents. Text sits at 0.93 lightness with a warm tint to kill haloing. Surface steps are tight and deliberate because shadows die in the dark. The entire contract lives in the split. Designers tune primitives. Engineers consume semantics. The system stays coherent when you add a third high contrast mode or let users tint backgrounds. The two layer split is what makes the map in the dark mode paper actually shippable. Primitives live in one collection per mode. Semantics stay constant. The alias layer is the glue. In CSS that means semantic custom properties declared once then primitive values redefined inside the data theme dark block. The blocking head script sets the initial theme before first paint so nothing flashes. In Figma you drop primitives into separate collections then build semantic variables that reference them by mode. Change the mode and the whole file updates. That is the architecture. It scales. It does not rot. It respects perceptual uniformity because OKLCH steps behave consistently across hues. A two layer token system is not the flat dump of 47 hex values most teams ship. It is not the 2018 pattern of sprinkling dark specific overrides across a single stylesheet then hunting bugs at 2 a.m. It is not an automated inversion script that flips lightness and calls the job done. Those paths produce grey porridge vibrating corals and text that burns retinas because they never separate the raw values from the roles. They make you rewrite components every time the brand color shifts. The two layer system prevents that maintenance nightmare by keeping semantics stable and isolating all perceptual tweaks inside the primitive layer. It demands you design dark mode as its own surface instead of a toggle. Concrete example. The working token map from the dark mode color systems paper is the reference implementation. Eleven primitives define everything. Background at oklch(0.10 0.012 30) sets a warm near black floor. Surface steps climb in 4 point lightness increments while chroma falls to stop muddy bleed. Text primitives use oklch(0.93 0.005 60) for primary copy to eliminate haloing and oklch(0.70 0.010 60) for secondary. The accent primitive lifts the brand hue 10 points in lightness then cuts chroma 22 percent so it reads as the same coral without searing against the dark field. Linear shipped this model in 2023. Their four tier surface primitives create seamless depth in the sidebar and issue list without a single shadow. Semantic tokens for every interactive state stayed untouched. Stripe applied the same split to dense financial dashboards the same year. Their semantic color surface for table rows maps to a warm tinted primitive in dark mode so traders can read numbers for hours without fatigue. The accent blue primitive gets recalibrated once and every CTA button chart and link inherits the fix. Vercel runs a binary version with only background and elevated surface primitives yet the semantic layer keeps their docs brutally consistent. GitHub added a dimmed primitive collection that sits warmer than their default dark. The semantics absorbed it so users could switch without breaking muscle memory. Arc takes it furthest by letting users inject custom hue primitives per space. The semantic tokens swallow those user values and the UI never fractures. Raycast uses aggressive lift on their orange accent primitive paired with heavy blur for implied elevation. Every example proves the same point. When primitives carry the perceptual load and semantics carry the role the system survives real product evolution. Use a two layer token system when you are building any product people will use past midnight or when brand recognition must survive theme changes. Deploy it for SaaS dashboards developer tools content editors or anything with complex surfaces like Notion Linear or Stripe. The upfront work pays off the first time you adjust one surface primitive and watch cards panels modals and text all update in both modes. It also shines when you later add a high contrast theme or user defined hues. You author new primitives and reuse every semantic token. No component audits. No support tickets about broken dark mode after an accent tweak. The structure enforces the desaturation rule the elevation model and the APCA plus eyeball contrast pass because all those checks happen at the primitive level. Skip the two layer system for simple marketing sites that live in light mode only. A one week landing page for a startup does not need the overhead. Solo founders building throwaway internal tools can hardcode a few hex values and move on. Avoid it if your team plans to treat dark mode as an afterthought because they will implement the layers wrong ignore the warm tint rule and still ship pure white text that halos. The architecture only delivers when the team commits to calibrating every primitive for real world perception instead of WCAG theater. The two layer approach also kills the shimmer halo and bloom problems at the source. You test specific primitive pairs with APCA then fix the central value once. Every usage updates. That centralization is what most half baked token setups promise but never achieve. Build your color system with two distinct layers or keep apologizing for dark mode that looks like it was designed at noon.

Related terms

Keep exploring