Token Resolution Order
Token Resolution Order
Token resolution order determines how final token values are computed when multiple definitions, overrides, or inheritance levels exist. In complex token systems with themes, brands, and component overrides, the same token name might have different potential values. Resolution order rules establish which value applies in any given context.
What Is Token Resolution Order
Token resolution order is the sequence of precedence rules that determines which token definition wins when multiple sources define the same token. This includes precedence between base tokens and theme overrides, between global tokens and component-scoped tokens, and between different inheritance layers.
Predictable resolution order enables developers to understand what value a token will have without tracing through complex conditional logic.
How Token Resolution Order Works
Resolution typically follows a layered approach where more specific definitions override more general ones.
Basic layer precedence (low to high):
- Base/default tokens
- Theme tokens
- Brand tokens
- Component tokens
- Instance overrides
A token defined at each layer would resolve to the instance override value.
Example with CSS custom properties:
/* Layer 1: Base */
:root {
--color-primary: blue;
}
/* Layer 2: Theme override */
[data-theme="dark"] {
--color-primary: lightblue;
}
/* Layer 3: Brand override */
[data-brand="secondary"] {
--color-primary: green;
}
/* Layer 4: Component scope */
.button {
--color-primary: purple;
}
An element within .button in dark theme with secondary brand would see:
- Which value? The component scope value (purple) because it is most specific in the cascade.
Build-time resolution for generated tokens:
// Resolution order configuration
const layers = [
'tokens/base/**/*.json',
'tokens/themes/dark/**/*.json',
'tokens/brands/secondary/**/*.json'
];
// Later files override earlier files for matching token names
const resolved = layers.reduce((acc, pattern) => {
const tokens = loadTokens(pattern);
return { ...acc, ...tokens };
}, {});
Key Considerations
- Resolution order should be documented and predictable
- Unexpected overrides indicate resolution order confusion
- Debugging tools should show resolution path
- Overly complex resolution creates maintenance burden
- CSS specificity interacts with token resolution on web
- Platform differences may affect resolution behavior
- Testing should verify resolution across contexts
- Clear naming can reduce resolution complexity
Common Questions
How does CSS specificity affect token resolution?
CSS specificity rules determine which property declaration applies when multiple rules target the same element. Token values delivered via CSS custom properties follow these same rules.
Specificity hierarchy:
- Inline styles (highest)
- ID selectors
- Class, attribute, and pseudo-class selectors
- Element and pseudo-element selectors
- Universal selector and inherited values (lowest)
Tokens and specificity:
/* Specificity: 0-1-0 (one class) */
.button {
--button-color: blue;
}
/* Specificity: 0-2-0 (two classes) */
.button.primary {
--button-color: green;
}
/* Specificity: 0-1-1 (one attribute + one element) */
[data-theme="dark"] button {
--button-color: lightblue;
}
Understanding CSS specificity helps predict which token value applies in complex styling scenarios.
Avoiding specificity wars:
- Keep selector specificity low and consistent
- Use data attributes for theme/state rather than deep class nesting
- Document expected specificity levels for token scopes
How should resolution be debugged?
Debugging resolution involves determining why a particular value applies.
Browser DevTools inspection: CSS custom properties appear in element inspection. The Computed tab shows the resolved value. The Rules tab shows which declarations apply.
Resolution tracing: Build tools can log resolution decisions:
function resolveWithTrace(tokenName, layers) {
const trace = [];
let value;
layers.forEach(layer => {
if (layer.tokens[tokenName]) {
trace.push({ layer: layer.name, value: layer.tokens[tokenName] });
value = layer.tokens[tokenName];
}
});
console.log(`Resolution trace for ${tokenName}:`, trace);
return value;
}
Documentation of overrides: Maintaining records of intentional overrides helps distinguish expected behavior from bugs.
What about resolution in JavaScript-based systems?
React, Vue, and other JS-based applications may implement token resolution through context or state.
React context resolution:
const TokenContext = createContext(baseTokens);
function TokenProvider({ theme, brand, children }) {
const tokens = useMemo(() => ({
...baseTokens,
...themeTokens[theme],
...brandTokens[brand]
}), [theme, brand]);
return (
<TokenContext.Provider value={tokens}>
{children}
</TokenContext.Provider>
);
}
Nested context override:
<TokenProvider theme="light">
{/* Uses light tokens */}
<TokenProvider theme="dark">
{/* Inner context overrides with dark tokens */}
</TokenProvider>
</TokenProvider>
JS-based resolution offers explicit control but requires careful context management. The merging strategy (shallow vs. deep) affects how nested token objects combine.
Summary
Token resolution order determines final values when multiple definitions exist for the same token. Layer-based precedence from general to specific provides predictable resolution. CSS specificity rules affect web-based token resolution. Debugging requires understanding both token layers and platform-specific resolution behavior. JavaScript-based systems implement resolution through context merging with explicit control over combination strategies.
Buoy scans your codebase for design system inconsistencies before they ship
Detect Design Drift Free