Why Naming Decisions Have Long-Term Consequences
Color naming in a design system is an architectural decision, not a stylistic one. Once a naming convention is established and adopted across a codebase and component library, changing it is expensive: every usage must be updated, every contributor must be re-educated, and the migration period where both old and new names coexist creates confusion. The naming convention chosen at the start shapes how the team reasons about color for as long as the system runs. Choosing the wrong model is recoverable, but only at significant cost. The decision deserves proportionate attention at system founding.
Scale-Based Naming
Scale-based naming assigns color positions along a lightness scale, typically using numbers (blue-100 through blue-900) or descriptive scales (blue-lightest through blue-darkest). The numbers or descriptors encode position without encoding intent — blue-600 says nothing about whether this value should be used for text, borders, backgrounds, or interactive states. This approach is flexible and composable: any component can use any scale value without violating a semantic rule. The weakness is that the appropriate use of each scale value must be documented externally and learned by contributors. Scale-based systems are susceptible to drift as different teams make independent choices about which value maps to which role. The advantage is that adding new colors or reorganizing the palette is straightforward — you are only rearranging primitives.
Semantic Naming
Semantic naming encodes the intended use directly into the token name: success, error, warning, background-primary, text-secondary, border-interactive. The name carries the usage contract — a developer using success-default knows it is for success states and cannot accidentally apply it to a background context without visibly violating the semantic convention. This approach reduces drift and makes the system self-documenting. The weakness is proliferation: as components multiply and edge cases accumulate, semantic systems grow large. A color used for both an interactive border and a focus ring may need two separate tokens to preserve semantic clarity, even if both point to the same underlying hex value. Large semantic systems can become difficult to navigate.
Two-Tier Architecture
The modern best practice is a two-tier approach that separates concerns: a primitive layer and a semantic layer. Primitives map meaningful names to raw values — blue-50 maps to #EFF6FF, blue-900 maps to #1E3A5F. Semantics map intent names to primitives — background-primary maps to blue-50, text-interactive maps to blue-700. Component code references only semantic tokens, never primitives directly. This separation enables theme changes (light to dark mode) by remapping the semantic layer to different primitives without changing any component code. Background-primary maps to blue-50 in light mode and to gray-950 in dark mode; components using background-primary automatically get the correct value in each theme.
Practical Naming Conventions
For the semantic layer, a consistent naming structure improves navigability: [component/context]-[role]-[variant]-[state]. For example: button-background-primary-default, button-background-primary-hover, text-heading-primary, border-input-default, border-input-error. For the primitive layer, a simple numeric scale works well: [hue]-[scale-number], where scale-number increases with lightness (50 very light, 900 very dark) or decreases with lightness (100 dark, 900 light) — choose one convention and be consistent. Document which convention your scale uses; both exist in the wild and mixing them across systems creates confusion. For semantic neutrals, warm-gray and cool-gray should be separate scales — treating all grays as a single scale obscures important temperature distinctions that affect visual tone.