When a design system spans multiple platforms—web, iOS, Android, and design tools like Figma or Sketch—keeping design tokens in sync becomes a persistent challenge. A color value updated in one place can take days to propagate everywhere, leading to visual inconsistencies and developer frustration. A custom token resolver addresses this by acting as a translation layer: it takes a canonical set of tokens and outputs platform-specific formats, handling naming conventions, value transformations, and override rules. In this guide, we walk through the why and how of building such a resolver, from core concepts to implementation steps and common pitfalls.
Why Token Drift Happens and Why a Resolver Matters
Design tokens are the atoms of a design system—values for colors, spacing, typography, shadows, and more. In a cross-platform setup, each platform has its own idioms: CSS uses #hex or rgba(), iOS uses UIColor with sRGB values, Android uses @color/ resources, and Figma uses PaintStyle objects. Without a resolver, teams manually copy values or rely on brittle scripts that break when token structures change.
The Cost of Manual Syncing
In a typical mid-size product team, a single color update might require changes in four or more locations: the design tool, the web app, the iOS app, and the Android app. Each update carries risk of human error—a typo in a hex code or a forgotten platform. Over time, these small errors compound, creating visual drift that erodes design consistency. A token resolver automates this translation, ensuring that a change to the canonical token file propagates to all platforms in a single step.
What a Token Resolver Does
At its core, a token resolver is a function or pipeline that takes a set of input tokens (usually in a standard format like JSON or YAML) and outputs platform-specific token files. It handles: mapping token names to platform conventions (e.g., color-primary to $color-primary in SCSS, Color.primary in Swift), transforming values (e.g., converting RGBA to HSLA or flattening shadows into platform-specific structs), and applying overrides (e.g., a different spacing scale for mobile vs. desktop).
When You Need a Custom Resolver
Off-the-shelf tools like Style Dictionary or Theo cover many use cases, but they may not handle every edge case: custom naming patterns, multi-brand token sets with conditional overrides, or integration with proprietary design tools. A custom resolver gives you full control over the transformation logic, enabling tighter integration with your existing build pipeline and design tool APIs. This article is for teams that have outgrown basic token export and need a scalable, maintainable solution.
Core Concepts: How Token Resolution Works
A token resolver typically follows a three-stage pipeline: parse, transform, and emit. Understanding each stage is key to designing a resolver that is both flexible and robust.
Parse: Reading the Canonical Source
The canonical token source is usually a single JSON or YAML file (or a folder of files) organized by category (color, spacing, typography) and optionally by theme or brand. The resolver reads this file and builds an in-memory tree of tokens. Each token has a name, a value, and metadata (e.g., description, aliases, platform overrides). For example:
{ "color": { "primary": { "value": "#1a73e8", "type": "color" }, "secondary": { "value": "#34a853", "type": "color" } } }Transform: Applying Platform Rules
During transformation, the resolver walks the token tree and applies a set of rules defined per platform. These rules can: rename tokens (e.g., camelCase for iOS, kebab-case for CSS), convert values (e.g., hex to UIColor), filter tokens (e.g., only output tokens marked for web), and resolve aliases (e.g., if color-primary references color-blue-500, the resolver inlines the final value). This stage is where most custom logic lives.
Emit: Generating Platform Files
Finally, the resolver writes the transformed tokens to files in the expected format: CSS custom properties, SCSS variables, Swift enums, Android XML resources, or even Figma JSON for a plugin. The output path and file structure should match the conventions of each platform's build system. For example, web tokens might go into a tokens/ folder, while iOS tokens are placed in a Swift package.
Comparison of Approaches
| Approach | Pros | Cons | Best For |
|---|---|---|---|
| Static Mapping (e.g., Style Dictionary) | Simple, declarative, good for standard formats | Limited custom logic, can be verbose for complex overrides | Teams with simple token structures and standard platforms |
| Runtime Transformation (e.g., custom Node script) | Full control, can integrate with APIs, handle dynamic overrides | More code to maintain, requires testing across platforms | Teams with complex rules, multi-brand, or proprietary tools |
| Hybrid (static config + custom transforms) | Balance of simplicity and flexibility | Two-layer configuration can be confusing | Teams that need custom transforms but want to reuse existing config |
Building Your Resolver: Step-by-Step Process
We recommend starting with a clear specification of your token schema and platform requirements. Then, iterate through the following steps.
Step 1: Define Your Canonical Token Schema
Choose a standard format like Design Tokens Format (W3C) or a simpler custom schema. Include fields for name, value, type, and optional platform overrides. For example: { "color-primary": { "$value": "#1a73e8", "$type": "color", "ios": { "value": "UIColor(red: 0.102, green: 0.451, blue: 0.910, alpha: 1.0)" } } }. This schema becomes your single source of truth.
Step 2: Map Platform Naming Conventions
Create a mapping file that defines how token names translate to each platform. For instance, a token named color-primary might become $color-primary in SCSS, Color.primary in Swift, and @color/primary in Android. Use a JSON config like: { "web": { "prefix": "$", "separator": "-" }, "ios": { "prefix": "", "separator": ".", "case": "pascal" } }.
Step 3: Implement Value Transformers
For each token type (color, spacing, shadow), write a transformer function that converts the canonical value to the platform-specific format. For colors, this might involve converting hex to UIColor or Android color int. For spacing, you might convert from rem to dp or pt. Use a registry pattern where each transformer is a function registered by type and platform.
Step 4: Handle Aliases and References
Tokens often reference other tokens (e.g., color-primary might be an alias for color-blue-500). Your resolver must resolve these chains before emitting. Implement a recursive resolution step that inlines the final value, with cycle detection to avoid infinite loops.
Step 5: Add Platform Overrides
Some tokens need different values per platform (e.g., a larger tap target on mobile). Allow overrides in the canonical schema, and have the resolver check for platform-specific values before falling back to the default. This keeps the canonical file clean while supporting platform nuances.
Step 6: Generate Output Files
Write a file generator for each platform. For CSS, output custom properties or SCSS variables. For iOS, generate a Swift file with an enum. For Android, output XML resources. Use templates or string builders to ensure consistent formatting. Integrate this step into your CI/CD pipeline so token updates trigger automatic regeneration.
Tools, Stack, and Maintenance Realities
Choosing the right stack for your resolver depends on your team's expertise and existing infrastructure. Here are common options and their trade-offs.
Node.js with TypeScript
Node.js is the most popular choice due to its rich ecosystem (Style Dictionary, Theo, and many utility libraries). TypeScript adds type safety for token schemas and transformer functions. The resolver can be a simple script run via npm or integrated into a build tool like Webpack or Vite. Maintenance involves updating transformers when platform SDKs change (e.g., new color spaces in iOS) and adding new token types.
Python for Data-Heavy Pipelines
If your token system involves complex data transformations or integration with design tool APIs (e.g., Figma REST API), Python's pandas and requests libraries can be powerful. However, Python is less common in frontend build pipelines, so you may need a bridge (e.g., a CLI tool called from npm).
Custom Figma Plugin
For design-to-code syncing, a Figma plugin can read tokens from the design file and output a JSON that your resolver consumes. This adds a step but ensures tokens are always up-to-date with the latest design changes. The plugin itself can be built with TypeScript and the Figma API.
Maintenance Considerations
A token resolver is not a set-it-and-forget-it tool. As your design system grows, you may need to: add new token types (e.g., motion tokens), support new platforms (e.g., Flutter), or update value formats (e.g., new color space standards). Plan for ongoing maintenance by writing unit tests for each transformer, documenting the schema and mapping rules, and setting up automated regression tests in CI. Also, consider versioning your token schema to allow gradual migration.
Growth Mechanics: Scaling Your Resolver for Multiple Brands and Teams
As your organization adopts the resolver, you may need to support multiple brands, themes, or product lines. This requires careful design of the token schema and resolver logic.
Multi-Brand Token Sets
Each brand may have its own color palette, typography, and spacing. One approach is to have a base token set (shared across brands) and brand-specific overrides. The resolver can load a brand-specific override file and merge it with the base set before transformation. For example, a brands/acme/overrides.json file that changes color-primary to a different value. This keeps the base tokens reusable while allowing brand differentiation.
Theme Support (Light/Dark/High Contrast)
Themes can be handled similarly: define theme-specific token values in the canonical file (e.g., color-background with light and dark variants). The resolver can output separate files per theme (e.g., tokens-light.css and tokens-dark.css) or use CSS custom properties with fallbacks. For mobile platforms, you might generate separate resource directories (e.g., values-night/ for Android).
Scaling the Team Workflow
As more teams adopt the resolver, establish governance: a central team maintains the canonical schema and core transformers, while product teams can propose new token types or overrides through a pull request process. Use semantic versioning for the token package so downstream consumers know when to update. Also, provide a migration guide and deprecation policy for old tokens.
Risks, Pitfalls, and Mitigations
Building a custom resolver comes with several risks. Here are common pitfalls and how to avoid them.
Token Name Collisions
When multiple teams contribute tokens, naming collisions can occur (e.g., two teams define color-primary with different meanings). Mitigate this by enforcing a naming convention with namespaces (e.g., button-color-primary) and using a linter in CI to detect duplicates.
Platform-Specific Overrides That Drift
Over time, platform overrides may become outdated as the base token changes. For example, a mobile override that was once necessary may no longer be needed after a design update. Mitigate this by flagging overrides that are close to the base value and requiring periodic review. Consider a dashboard that shows the delta between base and override values.
Performance Issues with Large Token Sets
If your token set grows to thousands of tokens (common in multi-brand systems), the resolver may become slow. Mitigate by using caching, incremental builds, and parallelizing file generation. Also, consider lazy-loading token categories that are not needed for every platform.
Breaking Changes in Platform SDKs
When Apple or Google updates their design language (e.g., new color space or typography scale), your transformers may break. Mitigate by abstracting platform-specific logic into separate modules that can be updated independently, and by writing integration tests that verify output against a known set of expected files.
Mini-FAQ: Common Questions About Token Resolvers
Should I use an existing tool like Style Dictionary instead of building a custom resolver?
Style Dictionary is excellent for standard use cases and can be extended with custom transforms. If your needs are simple (e.g., one brand, web only), start with Style Dictionary. However, if you need complex override logic, multi-brand support, or integration with proprietary tools, a custom resolver gives you more flexibility. Many teams start with Style Dictionary and later migrate to a custom solution as requirements grow.
How do I handle token aliases that reference other aliases?
Implement a recursive resolution with a visited set to detect cycles. Use a depth-first search that resolves each alias to its final value, checking for circular references. If a cycle is detected, throw an error with the token name for debugging.
Can I use the resolver to sync tokens back to the design tool?
Yes, you can reverse the pipeline: read platform-specific token files and transform them into a format that a design tool plugin can import (e.g., Figma JSON). This enables a bidirectional sync, though it requires careful handling of naming and value conversions. Start with unidirectional (design to code) and add reverse sync only if needed.
What is the best format for the canonical token file?
The W3C Design Tokens Format is gaining traction and is supported by many tools. It provides a standard structure for type, value, and descriptions. If you need platform overrides, you can extend it with custom properties (e.g., $extensions). Avoid inventing a completely custom format unless you have very specific needs.
Next Steps: From Prototype to Production
Start small: pick one platform (web) and one token type (colors) to build a minimal resolver. Test it with a real design system file and verify the output matches expected values. Then, gradually add more platforms and token types, using the lessons learned to refine your architecture. Document your schema, transformers, and naming conventions so that new team members can contribute. Finally, integrate the resolver into your CI/CD pipeline so that token updates are automatically propagated. A well-built token resolver is an investment that pays for itself in reduced manual work and increased consistency across your product ecosystem.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!