The semantic layer is the hard part
April 7, 2026
A team spends a week on a rebrand. Four base token values change — a warmer red, a slightly different blue, two adjusted neutrals. Twenty minutes in the token editor. Three weeks later, the engineering team is still fixing regressions. Dark mode error states are the wrong color. A secondary button is showing the old primary. A feedback banner somehow escaped the update entirely. The base layer was fine. The semantic layer was not, and the rebrand just made that visible.
This is the most common design system failure mode, and it is almost never diagnosed correctly. Teams blame the tooling or the process. The real cause is that their semantic layer was built around current values instead of stable decisions.
What a semantic token actually encodes
When most teams build a semantic layer, they think about naming. color.feedback.error instead of color.red-500. That is the right instinct, but it is only the beginning.
A semantic token encodes a decision: when and why a value should be used. color.feedback.error does not mean "red." It means "the value to use when something has failed and the user needs to act." That decision is stable. It survives rebrands, platform expansions, dark mode. The value it points to may change entirely. The decision it represents does not.
The aliasing trap
Most teams start with a flat two-tier alias: color.feedback.error -> color.palette.red-500. Reasonable. The error color is red, so point it at the red value. This works fine until you add dark mode.
In a dark context, color.palette.red-500 is too bright. So you add an override: color.feedback.error -> color.palette.red-400 for dark mode. Now the semantic token is carrying context it was never designed for. You have two aliases for the same decision, and nothing in the structure explains why they are different or when each applies.
A mid-tier token resolves this. Instead of pointing directly at a palette value, you write color.feedback.error -> color.status.negative. color.status.negative is the mid-tier token. It holds the decision: the appropriate value for a negative status signal in the current context. In light mode it resolves to red-500. In dark mode, red-400. The semantic token does not know or care which. It references a decision, not a value.
The mid-tier holds the context-sensitive decision. The semantic token just references it.
Dark mode is the test
If switching from light to dark requires changing more than 20% of your semantic alias targets, the semantic layer was reflecting the light palette, not intent. A well-built semantic layer has the same token names across modes. Only the base values change. color.feedback.error means the same thing in light and dark mode. What it resolves to changes. If the alias itself has to change when you switch modes, that token was encoding the current value, not the concept.
This matters well beyond dark mode. Brand variants, density scales, platform differences — all of these are mode-like problems. Systems that survive them tend to have a semantic layer built around decisions. Systems that collapse mid-rebrand tend to have a semantic layer that was actually just documentation for the light palette. For more on what alias changes cost at scale, see the post on design token versioning.
Catching bypasses before they compound
Even with a well-structured semantic layer, engineers under deadline pressure will reach for a raw value. A hardcoded #ef4444 in a component file bypasses the entire aliasing system. When the brand updates, that value does not move with everything else.
reframe lint scans your codebase for raw values that match token values and flags them with the token they should reference instead. Running it in CI catches these bypasses before they accumulate. A single hardcoded value in one component is a minor cleanup. Fifty of them across a dozen components, discovered mid-rebrand, is a week of archaeology.
The shape of a stable aliasing strategy
Three tiers, with a clear job at each level.
Base holds raw values. color.palette.red-500 = #ef4444. Internal only. Consumer apps never reference this layer.
Mid-tier holds context-sensitive decisions. color.status.negative, color.action.primary, color.surface.overlay. These tokens answer: what is this for, and how does that answer change by context? Most aliasing complexity should live here.
Semantic aliases mid-tier tokens for specific use cases. color.feedback.error -> color.status.negative. Consumer apps reference this layer. The semantic token does not know anything about modes or palette values — and it should not.
The teams that come through rebrands without a three-week fixing period are not luckier than the ones that do not. They built the semantic layer around what things mean rather than what they currently look like. That difference is invisible until the first major brand change, at which point it is the most important thing in the system.