The three-tier color naming model separates colors into: (1) Primitive tokens — the raw color values with descriptive names (blue-500, gray-100). These are fixed values that never change meaning. (2) Semantic tokens — purpose-driven names that reference primitives (color-background, color-interactive, color-error). These have stable meaning but can change their underlying primitive across themes. (3) Component tokens — component-specific names that reference semantic tokens (button-background, card-border, nav-text). These are the highest-level abstraction and exist only for components that need fine-grained control. The most common mistake: systems that skip tier 2 (semantic tokens) and map primitive names directly to component names. This collapses when the team needs theming, dark mode, or brand refreshes — without a semantic layer, every theme requires a new primitive-to-component mapping, and the work scales with (primitives x themes).
Primitive token naming should be value-descriptive, never semantic. `blue-500` is correct; `brand-blue` is incorrect for this tier because it encodes meaning (brand association) into a structural name (the 500 step in the blue scale). When the brand color changes from blue to green, `brand-blue` must be renamed everywhere, whereas `blue-500` simply becomes a less frequently referenced value. The numeric scale convention (50-950 or 100-900 in 100-step increments) has become the de facto standard because it is extensible and value-descriptive — `blue-450` clearly represents a value between blue-400 and blue-500. Avoid naming primitives with lightness words (blue-light, blue-dark) because these become ambiguous at the edges and when the scale grows — is `blue-lighter` lighter than `blue-light`? The numeric convention avoids this entirely.
The dark mode collapse is the most common symptom of a broken semantic layer. Teams building dark mode for the first time often discover that they have no semantic tokens — their components reference primitive tokens directly (color: blue-900; background: gray-50). Dark mode requires a different color assignment in a different context, which requires a semantic layer to express: `color-text: blue-900 (light) / blue-100 (dark)`. Without this layer, every component needs manual dark mode overrides, which is unsustainable. The fix requires retroactively adding a semantic layer between primitives and components — auditing which primitives appear in which semantic roles, creating semantic tokens that cover those roles, updating components to reference semantic tokens, and then defining the dark mode theme as a mapping of semantic tokens to different primitives. This is correct but painful to do after the fact; the cost of doing it correctly at the start is low.