Deep Dive: Resolver Module
Why a Resolver Module?
Simple token systems resolve references by direct substitution: {core.color.blue.500} becomes #0a65fe. But real-world systems need more: brand switching, theme variants, platform-specific values, and conditional overrides.
The DTCG 1.0 Resolver Module provides a standardized approach to context-aware token resolution. It defines how token sets combine, which modifiers apply, and in what order conflicts are resolved.
Resolver Document Structure
A resolver document is a JSON file that defines the resolution strategy for a token system:
{
"$schema": "resolver.schema.json",
"name": "Portfolio Design System Resolver",
"version": "2025-01-01",
"description": "Context-aware token resolution",
"sets": { ... }, // Token collections
"modifiers": { ... }, // Value transformations
"resolutionOrder": [ ... ] // Precedence rules
}Core Properties
| Property | Purpose |
|---|---|
sets | Named collections of tokens that can be combined |
modifiers | Transformations applied to resolved values |
resolutionOrder | Sequence determining which values win conflicts |
Sets: Token Collections
Sets define collections of tokens that can be combined during resolution. Each set can reference external files or define inline tokens.
File-Based Sets
{
"sets": {
"foundation": {
"description": "Core design tokens",
"sources": [
{ "$ref": "core.tokens.json" }
]
},
"semantic": {
"description": "Semantic tokens referencing foundation",
"sources": [
{ "$ref": "semantic.tokens.json" }
]
},
"brand-acme": {
"description": "Acme brand overrides",
"sources": [
{ "$ref": "brands/acme.tokens.json" }
]
}
}
}Inline Sets
For small overrides or runtime values, sets can define tokens inline:
{
"sets": {
"runtime-overrides": {
"description": "Dynamic runtime values",
"tokens": {
"color": {
"brand": {
"primary": {
"$type": "color",
"$value": "#custom-brand-color"
}
}
}
}
}
}
}Set Composition
Sets can reference other sets, building layers of tokens:
{
"sets": {
"foundation": {
"sources": [{ "$ref": "core.tokens.json" }]
},
"semantic": {
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "semantic.tokens.json" }
]
},
"brand-complete": {
"sources": [
{ "$ref": "#/sets/semantic" },
{ "$ref": "brand.tokens.json" }
]
}
}
}Modifiers: Context Dimensions
Modifiers define dimensions of variation like theme, platform, or accessibility settings. Each modifier has contexts that can be activated.
Theme Modifier
{
"modifiers": {
"theme": {
"description": "Color theme (light/dark)",
"default": "light",
"contexts": {
"light": {
"description": "Light theme",
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" }
]
},
"dark": {
"description": "Dark theme",
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" },
{ "$ref": "themes/dark.tokens.json" }
]
}
}
}
}
}Platform Modifier
{
"modifiers": {
"platform": {
"description": "Target platform",
"default": "web",
"contexts": {
"web": {
"sources": [{ "$ref": "platforms/web.tokens.json" }]
},
"ios": {
"sources": [{ "$ref": "platforms/ios.tokens.json" }]
},
"android": {
"sources": [{ "$ref": "platforms/android.tokens.json" }]
}
}
}
}
}Accessibility Modifier
{
"modifiers": {
"accessibility": {
"description": "Accessibility preferences",
"default": "default",
"contexts": {
"default": {
"sources": []
},
"high-contrast": {
"sources": [{ "$ref": "a11y/high-contrast.tokens.json" }]
},
"reduced-motion": {
"sources": [{ "$ref": "a11y/reduced-motion.tokens.json" }]
}
}
}
}
}Resolution Order
The resolution order defines precedence when multiple sets define the same token. Later entries override earlier ones.
Basic Resolution Order
{
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" },
{ "$ref": "#/modifiers/theme" }
]
}This means:
- Foundation tokens load first (base values)
- Semantic tokens override foundation where they conflict
- Theme modifier applies last, overriding both
Complex Resolution Order
{
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" },
{ "$ref": "#/modifiers/platform" },
{ "$ref": "#/modifiers/theme" },
{ "$ref": "#/modifiers/accessibility" },
{ "$ref": "#/sets/brand-overrides" }
]
}Resolution Contexts
When resolving tokens, you provide a context that specifies which modifier values to use:
// Resolution context
const context = {
theme: "dark",
platform: "ios",
accessibility: "high-contrast"
};
// Resolver applies context to resolution order
const resolvedTokens = resolver.resolve(context);Context Examples
| Scenario | Context |
|---|---|
| Web, light theme | { platform: "web", theme: "light" } |
| iOS, dark theme | { platform: "ios", theme: "dark" } |
| High contrast mode | { accessibility: "high-contrast" } |
| Brand A, dark, Android | { brand: "acme", theme: "dark", platform: "android" } |
JSON Pointer References
The resolver uses JSON Pointer syntax for precise references within and across documents:
Internal References
// Reference a set defined in the same document
{ "$ref": "#/sets/foundation" }
// Reference a modifier context
{ "$ref": "#/modifiers/theme/contexts/dark" }External References
// Reference an external file
{ "$ref": "core.tokens.json" }
// Reference a specific path in an external file
{ "$ref": "core.tokens.json#/color/palette/blue" }Our Resolver Configuration
Here's our actual resolver configuration that handles foundation, semantic, and theme contexts:
// ui/designTokens/resolver.json
{
"$schema": "../utils/designTokens/validators/resolver.schema.json",
"name": "Portfolio Design System Resolver",
"version": "2025-01-01",
"sets": {
"foundation": {
"description": "Core design tokens",
"sources": [{ "$ref": "core.tokens.json" }]
},
"semantic": {
"description": "Semantic tokens",
"sources": [{ "$ref": "semantic.tokens.json" }]
}
},
"modifiers": {
"theme": {
"description": "Color theme modifier",
"default": "light",
"contexts": {
"light": {
"description": "Light theme",
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" }
]
},
"dark": {
"description": "Dark theme",
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" }
]
}
}
}
},
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" },
{ "$ref": "#/modifiers/theme" }
]
}Build System Integration
The resolver integrates with the build system to generate context-specific outputs:
// Build configuration
export default {
resolver: './ui/designTokens/resolver.json',
outputs: [
{
context: { theme: 'light' },
css: './dist/tokens-light.css'
},
{
context: { theme: 'dark' },
css: './dist/tokens-dark.css'
},
{
context: { theme: 'light', platform: 'ios' },
swift: './dist/Tokens.swift'
}
]
};Runtime Resolution
import { Resolver } from '@/utils/designTokens/resolver-module';
import resolverConfig from '@/ui/designTokens/resolver.json';
// Create resolver instance
const resolver = new Resolver(resolverConfig);
// Resolve for specific context
const lightTokens = resolver.resolve({ theme: 'light' });
const darkTokens = resolver.resolve({ theme: 'dark' });
// Get specific token value
const primaryColor = resolver.getToken(
'semantic.color.foreground.primary',
{ theme: 'dark' }
);Pitfalls to Avoid
1. Circular Set References
Sets cannot reference themselves or form cycles. The resolver detects and rejects circular dependencies.
// BAD: Circular reference
{
"sets": {
"a": { "sources": [{ "$ref": "#/sets/b" }] },
"b": { "sources": [{ "$ref": "#/sets/a" }] }
}
}
// GOOD: Linear dependency chain
{
"sets": {
"foundation": { "sources": [{ "$ref": "core.tokens.json" }] },
"semantic": { "sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "semantic.tokens.json" }
]}
}
}2. Missing Default Contexts
Every modifier should have a default context. Without it, resolution fails when no context is provided.
// BAD: No default
{
"modifiers": {
"theme": {
"contexts": {
"light": { ... },
"dark": { ... }
}
}
}
}
// GOOD: Default specified
{
"modifiers": {
"theme": {
"default": "light",
"contexts": {
"light": { ... },
"dark": { ... }
}
}
}
}3. Inconsistent Token Coverage
All contexts within a modifier should define the same tokens. Otherwise, some contexts will have missing values.
// BAD: Dark theme missing tokens
{
"contexts": {
"light": {
"sources": [
{ "$ref": "colors.json" },
{ "$ref": "spacing.json" }
]
},
"dark": {
"sources": [
{ "$ref": "colors-dark.json" }
// Missing spacing!
]
}
}
}
// GOOD: Consistent coverage
{
"contexts": {
"light": {
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" }
]
},
"dark": {
"sources": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" },
{ "$ref": "themes/dark-overrides.json" }
]
}
}
}4. Wrong Resolution Order
Resolution order matters. More specific overrides should come later. Foundation before semantic, semantic before brand.
// BAD: Brand before semantic (brand values get overwritten)
{
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/brand" },
{ "$ref": "#/sets/semantic" }
]
}
// GOOD: Most specific last
{
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/semantic" },
{ "$ref": "#/sets/brand" }
]
}Summary
- Sets define collections of tokens that can be combined
- Modifiers define variation dimensions (theme, platform, accessibility)
- Resolution order determines which values win conflicts
- Contexts specify which modifier values to use during resolution
- JSON Pointers enable precise internal and external references
- Build integration generates context-specific outputs
Next Steps
The resolver module works alongside multi-brand theming to enable complex scenarios. For simpler setups, the $extensions.design.paths approach in core vs semantic may be sufficient. See schema validation for how resolver documents are validated.
Source files:
ui/designTokens/resolver.json— Our resolver configurationutils/designTokens/utils/resolver-module.ts— Resolver implementationutils/designTokens/validators/resolver.schema.json— JSON Schema