From 30de46cd226deca41b486640b1443f58b28a369d Mon Sep 17 00:00:00 2001 From: Mike Kamminga <988169+mikekamminga@users.noreply.github.com> Date: Tue, 29 Jul 2025 13:32:09 +0200 Subject: [PATCH 1/9] Adding technical report: Resolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds the Resolver specification working document which was orginally created by the team at Hyma/Tokens Studio as a contribution to the DTCG working group and the evolution of Design Tokens standards and ecosystem. All comments from the source Google doc — where earlier collaration found place — which were open/unresolved have been included in this version as ISSUE blocks throughout the document and need to be discussed and resolved. --- technical-reports/resolver/CHANGELOG.md | 50 + technical-reports/resolver/index.html | 1627 +++++++++++++++++++++++ 2 files changed, 1677 insertions(+) create mode 100644 technical-reports/resolver/CHANGELOG.md create mode 100644 technical-reports/resolver/index.html diff --git a/technical-reports/resolver/CHANGELOG.md b/technical-reports/resolver/CHANGELOG.md new file mode 100644 index 0000000..a447c3e --- /dev/null +++ b/technical-reports/resolver/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [2.1.0] - 2025-07-23 + +This release incorporates editorial improvements and community feedback summarized from the [Design Tokens Resolvers Specification Working Copy](https://docs.google.com/document/d/1LOtdiS8R903R7RwDd22JiDxljh51l7Xfy9M1D-p-9mU/edit?tab=t.0#heading=h.svkctwfaregs), identifying key areas for future specification development. + +### Added + +- **Resolution Aliasing Section:** Added a complete new section explaining aliasing/namespacing concepts with detailed examples and JSON Schema definition (from Working Copy) +- **Community Feedback Integration:** Incorporated editorial comments and issues from the working copy document throughout the specification, highlighting ambiguities, inconsistencies, and areas needing clarification. + +### Issues Identified (from Working Copy) + +- **Terminology Clarifications:** Highlighted need for better definitions of "process", "inputs", "dimensions" vs "contexts", and disambiguation between different types of aliasing. +- **File Extension Recommendations:** Suggested using `.tokens.json` extension to align with Design Tokens Format Specification conventions. +- **Schema Structure Improvements:** Identified need to move functional properties out of generic `meta` property into formal schema definitions. +- **Modifier Structure Concerns:** Raised questions about using arrays vs objects for modifiers to ensure uniqueness and prevent conflicts. +- **Merging Logic Specification:** Highlighted need for detailed DTCG-compliant merging algorithms and conflict resolution rules. +- **Precedence and Order:** Identified need for explicit precedence rules when multiple modifiers affect the same tokens. +- **Orthogonality Declaration:** Suggested need for explicit orthogonality declarations to support lazy resolution. + +### Notes + +- This version focuses on integrating community feedback and issue identification from the working copy rather than normative specification changes. +- Issues summarized from the working copy will inform future specification development and clarifications. + +## [2.0.0] - 2023-10-27 + +This is the first major revision of the specification based on a detailed technical review. The goal of this release is to add clarity, address ambiguities, and provide a more robust foundation for implementers. + +### Added + +- **"Include" Modifier Type:** Added a new `include` type for modifiers, which is used to conditionally include a set of tokens. An example has been added to the "Modifiers" section. +- **Order of Precedence:** A new subsection, "Order of Precedence," has been added to the "Resolution Logic" section to explicitly define the merge order for base sets and modifiers. +- **Error Handling Guidance:** A new informative section, "Error Handling," has been added to recommend specific error types for common failure scenarios (e.g., `FileNotFoundError`, `CircularReferenceError`). + +### Changed + +- **Modifier Type:** The `type` property on modifiers now defaults to `"enumerated"`. +- **Inline Token Definitions:** Clarified that an "inline token definition" must be a complete JSON object representing a valid token structure. An example has been added to the "Token Sets" section. +- **Final Output Format:** The specification now explicitly states that the final resolved output should be a nested JSON object that mirrors the token paths, as shown in the examples. +- **Path Resolution:** It is now explicitly stated that file paths in a resolver file must be resolved relative to the location of the resolver file itself. +- **Alias Resolution Scope:** The spec now clarifies that alias resolution is performed on the fully merged set of tokens, allowing aliases to reference tokens across any loaded file. +- **`meta.alias` Behavior:** The behavior of `meta.alias` is now more clearly defined, explaining that it namespaces the tokens from the modifier's files and that external references must use this namespace. + +### Fixed + +- **Inconsistent `\$value` Key:** Corrected all instances of an inconsistent `value` key in JSON examples to use `\$value`, aligning with the Design Tokens Format Specification. \ No newline at end of file diff --git a/technical-reports/resolver/index.html b/technical-reports/resolver/index.html new file mode 100644 index 0000000..0637abe --- /dev/null +++ b/technical-reports/resolver/index.html @@ -0,0 +1,1627 @@ + + + + + Design Tokens Resolver Module + + + + +
+

+ This document describes the technical specification for a resolver mechanism used to manage and resolve design tokens in complex scenarios involving themes, modes, brands, and other modifiers. It extends the Design Tokens Format Specification by introducing a structured way to combine and override token sets to produce a final, resolved set of tokens for consumption by design tools and platforms. +

+
+ +
+

+ This is a snapshot of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C or the Design Tokens W3C Community Group Membership. Don’t cite this document other than as work in progress. +

+

+ This document has been published to facilitate Wide Review. +

+

+ This document was produced by the Design Tokens W3C Community Group, and contributions to this draft are governed by Community Contributor License Agreement (CLA), as specified by the W3C Community Group Process. +

+
+ +
+

Introduction

+

+ Design tokens are key-value pairs that represent design decisions, allowing for consistent use across different platforms and tools. In complex design systems, there is a need to manage multiple dimensions such as themes, modes (e.g., light and dark), brands, and variants. This introduces challenges in organizing tokens, resolving references, and applying overrides. +

+

+ The Resolver Specification addresses these challenges by introducing a structured mechanism to: +

+ +
+ +
+

Rationale

+
+

Context and Problem Statement

+

In design systems, especially those at scale, managing multiple themes, modes, brands, and other modifiers can become complex. Designers and developers often struggle with:

+ +
+
+

Parameters

+ +
+
+

Background

+

Existing approaches, such as embedding modifiers directly into token names or using global theme files, have limitations. They often lead to:

+ +
+
+

Motivation

+

The Resolver Specification aims to:

+ +
+
+ +

+ A side effect of using a resolver should be the ability to have a clear dependency graph of how a resolution request would occur purely based on the inputs. This would enable better understanding of modifier interactions and support more efficient resolution strategies. +

+ +
+

Resolver Terminology

+
+
Resolver
+
A mechanism that combines token sets and applies modifiers to produce a final, resolved set of design tokens.
+
Token Set
+
A collection of design tokens grouped together, often representing foundational tokens, component-specific tokens, or theme-specific tokens.
+
Dimension
+
Dimensions are categories used to organize your design tokens. See them as contexts in which token values might change:
+ +

+ The term "Dimension" is potentially confusing since "Dimension" is also a type of token and a general concept. Consider using "Context" instead, which aligns with the existing definition as "contexts in which token values might change." Additionally, explicit mention of color schemes may be problematic - developers often distinguish between "themes" and "color modes" (like Windows High Contrast mode for accessibility), and it's unclear whether everyone would understand themes to encompass dark/light/high-contrast modes. +

+
Modifier
+
+ An entity that modifies or overrides tokens in the base sets. Modifiers can represent dimensions like themes, modes, brands, or any other contextual variations. +
+
Enumerated Modifier
+
A modifier with predefined, named values (e.g., "light", "dark" for a theme modifier).
+
Include Modifier
+
A modifier that replaces or includes entire token sets during resolution.
+
Alias
+
An optional property that allows for namespacing or renaming token paths during resolution.
+
+

+ The modifier types and their behaviors aren't clear from these definitions alone. Real-world examples showing how Enumerated Modifiers, Include Modifiers, and Alias properties work in practice would help clarify their purpose and usage. +

+
+
Input
+
Parameters provided to the resolver to specify which modifiers to apply during the resolution process.
+
Resolution
+
The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens.
+

+ The specification should address different real-world resolution use cases and their performance implications: full upfront resolution (resolving all possible combinations ahead of time), lazy resolution (resolving on-demand), and partial resolution for complex components. Without being upfront about token combinations and scopes, systems face combinatorial explosion problems that make resolution inefficient. There may be significant performance aspects to consider, especially for JIT (Just-In-Time) resolution scenarios. Input from tool makers like Figma and other design platforms would be valuable to understand real-world performance requirements and constraints. +

+
Orthogonality
+
The property of modifiers being independent of each other, allowing them to be combined freely without affecting each other's resolution logic.
+
+
+ +
+

Resolver File Format

+

A resolver is defined as a JSON object with the following properties:

+ + +
+

Token Sets

+

Each token set in the sets array is an object with the following properties:

+ +

Example:

+
+{
+  "sets": [
+    {
+      "name": "foundation",
+      "values": ["foundation.json"]
+    },
+    {
+      "values": [
+        "components/button.json",
+        {
+          "inline-token": { "$value": "some-value" }
+        }
+      ]
+    }
+  ]
+}
+        
+

+ It is recommended to use .tokens.json as the file extension for token files to align with the Design Tokens Format Specification naming conventions. This helps reinforce that these files should contain valid DTCG token structures rather than arbitrary JSON data. +

+
+ +
+

Modifiers

+

Modifiers are defined in the modifiers array and can have different types, affecting how they influence the resolution process.

+

Each modifier is an object with the following properties:

+ +

+ Default values and aliasing should be moved out of the generic meta property into specific typed properties. The meta property should be reserved for implementation-specific extensions rather than functionality that directly contributes to resolution behavior. +

+

Example of an "enumerated" modifier:

+
+{
+  "modifiers": [
+    {
+      "name": "theme",
+      "type": "enumerated",
+      "values": [
+        {
+          "name": "light",
+          "values": ["themes/light.json"]
+        },
+        {
+          "name": "dark",
+          "values": ["themes/dark.json"]
+        }
+      ],
+      "meta": {
+        "default": "light",
+        "alias": "theme"
+      }
+    }
+  ]
+}
+        
+

+ There are structural problems with using arrays for modifiers and their values. Since name is required and must be unique for proper resolution, arrays allow for multiple conflicting values which should be impossible. For example, if a user passes { theme: "dark" } and there are 2 modifiers named "theme" and 2 modifier values named "dark", this creates ambiguity that should be an error rather than defaulting to "take the first one." While arrays make sense for defining order of application, if we want key-value mapping for applying modifiers, flat objects should be required to ensure unique IDs by design. +

+

+ The specification needs to clarify what constitutes valid values in the values array for modifiers. Key questions include: Are only relative pathnames allowed? Can referenced files have sets/modifiers of their own (enabling resolver chaining)? Should there be support for referencing sets by name (e.g., "foundation") rather than repeating file paths? The concept of chaining resolvers could use different type values to identify other resolvers. Additionally, values could support local references starting with "#" or string references to set names, making it easier to reference entire sets without listing all token file paths. +

+ +

Example of an "include" modifier. This type of modifier is used to conditionally include a set of tokens. The `values` array for an include modifier contains objects with a `name` and a corresponding list of `values` (file paths or inline tokens) that will be included if that name is present in the input.

+
+{
+  "modifiers": [
+    {
+      "name": "features",
+      "type": "include",
+      "values": [
+        {
+          "name": "experimental-feature-x",
+          "values": ["features/feature-x.json"]
+        }
+      ]
+    }
+  ]
+}
+        
+
+
+ +
+

Resolution Aliasing

+

Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts.

+
+

Namespace vs Alias Terminology: Should the alias property be renamed to namespace to avoid confusion with token aliases (references to other tokens)?

+

Redundancy with Modifier Names: The meta.alias property may be redundant since modifiers already have a name property that could serve the same namespacing purpose.

+

Auto-namespacing Concerns: The automatic application of namespacing for modifiers introduces significant complexity and potential issues:

+ +

Alternative Approach: Consider removing auto-namespacing for modifiers and using shared merging logic between sets and modifiers for better compatibility and simplicity.

+
+

Example:

+

Given a token set size.json:

+
+{
+  "sm": {
+    "value": "1px",
+    "type": "dimension"
+  },
+  "lg": {
+    "value": "10px",
+    "type": "dimension"
+  }
+}
+      
+

By applying an alias in the modifier's meta.alias, we can namespace these tokens:

+
+{
+  "modifiers": [
+    {
+      "name": "size",
+      "type": "include",
+      "values": [
+        {
+          "name": "default",
+          "values": ["size.json"]
+        }
+      ],
+      "meta": {
+        "alias": "spacing"
+      }
+    }
+  ]
+}
+      
+

Resulting in tokens accessible via spacing.sm and spacing.lg.

+

+ If the meta.alias behavior described above is normative/required behavior, it should not be part of the generic meta property but should be defined as part of the formal schema. Alternatively, if this is just an example of tooling-specific behavior, it should be clearly called out as such and not presented as part of the core specification. +

+ +
+

JSON Schema

+

Source: https://resolver-spec.netlify.app/reference/schema/

+
+{
+  "$id": "https://schemas.tokens.studio/prototype/resolver.json",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "description": "Resolver Specification",
+  "$defs": {
+    "tokenSet": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "values": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      },
+      "required": ["values"]
+    },
+    "modifier": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "values": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string"
+              },
+              "values": {
+                "type": "array",
+                "items": {
+                  "type": "string"
+                }
+              }
+            },
+            "required": ["name", "values"]
+          }
+        },
+        "meta": {
+          "type": "object",
+          "additionalProperties": true
+        }
+      },
+      "required": ["name", "values"]
+    }
+  },
+  "type": "object",
+  "properties": {
+    "name": {
+      "type": "string"
+    },
+    "description": {
+      "type": "string"
+    },
+    "sets": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/tokenSet"
+      }
+    },
+    "modifiers": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/modifier"
+      }
+    }
+  },
+  "required": ["sets", "modifiers"]
+}
+         
+
+
+ +
+

Resolution Logic

+

The resolution process involves the following steps:

+
    +
  1. Input Validation: Ensure the provided inputs match the expected modifiers and their acceptable values.
  2. +

    + The term "inputs" is used throughout the specification but never formally defined. Inputs appear to represent the permutation or combination of modifiers being selected for resolution - essentially the question "What would my output look like given some combination of modifiers being enabled or chosen?" This fundamental concept should be clearly defined, potentially as "data" or with a more specific term that clarifies its role in the resolution process. +

    +
  3. Base Set Flattening: Load and merge the token sets specified in the sets array. Later sets override earlier ones if there are naming conflicts.
  4. +

    + The merging process needs detailed specification to handle DTCG token data correctly. Simply performing a deep merge on raw JSON could yield different results than fully interpreting the DTCG data first and then merging. This is particularly important for inheritable format properties like $type. For example, when loading two JSON files with a top-level "color" key where one has $type: "color" and another has $type: "gradient", these represent incompatible schemas that cannot be merged. The specification must outline what constitutes "mergeable" vs "unmergeable" token sets and provide clear rules for handling such conflicts. +

    +

    + The specification needs clear step-by-step instructions for how deep merging should occur, including detailed guidance on handling edge cases or when to error appropriately. This should include specific algorithms and decision trees for implementers to follow consistently. +

    +
  5. Modifier Application: Apply the selected modifiers based on the inputs. Modifiers can override tokens from the base sets or introduce new tokens.
  6. +

    + The specification should clarify the resolution order when multiple modifiers are applied simultaneously. For instance, if both "theme" and "brand" modifiers are used and both attempt to override the same token, which modifier should take precedence? Clear precedence rules are needed to ensure consistent and predictable resolution behavior across implementations. +

    +
  7. Aliasing and Namespacing: Apply any aliasing specified in the modifiers to namespace or rename tokens.
  8. +

    + The specification needs to clarify the different types of "aliasing" and their behavior during resolution. There are two distinct concepts being referred to as "aliasing": (1) Namespacing aliasing - where tokens are renamed/namespaced (e.g., "red.500" becomes "colors.red.500"), and (2) Token reference aliasing - where one token's value references another token's value. The specification should clearly distinguish between these concepts and address how namespacing aliases behave when tokens are overridden by later sets - specifically, if an alias points to a token that gets overridden, does the alias resolve to the new/overridden value? +

    +
  9. Alias Resolution: Resolve any token references (aliases) using the combined tokens from the base sets and modifiers.
  10. +
  11. Conflict Resolution: In case of conflicting tokens (same name and path), later modifiers or sets override earlier ones.
  12. +
  13. Circular Reference Detection: Detect and handle circular references as errors.
  14. +
  15. Final Output: Produce a flat, resolved set of tokens ready for consumption.
  16. +
+ +
+

Detailed Steps

+
+

Input Validation

+
    +
  • Verify that all provided modifier inputs correspond to defined modifiers.
  • +
  • Check that the input values are among the acceptable options defined in the modifiers.
  • +
+
+
+

Base Set Flattening

+
    +
  • Load each token set in the order specified.
  • +
  • Merge the tokens, with later sets overriding earlier ones on name conflicts.
  • +
+
+
+

Modifier Application

+
    +
  • For each modifier: +
      +
    • Apply any aliasing or namespacing specified in meta.
    • +
    • Load the token sets associated with the selected modifier value.
    • +
    • Merge these tokens with the base tokens, applying overrides as necessary.
    • +
    +
  • +
+
+
+

Alias Resolution

+

Alias resolution is performed on the fully merged set of tokens, after all base sets and modifiers have been applied. This allows for aliases to reference tokens from any loaded file.

+
    +
  • Iterate over all tokens to find references (e.g., {theme.accent}).
  • +
  • Resolve references first within the same token set.
  • +
  • If not found, resolve references from the modifiers, following the order of precedence.
  • +
  • Handle nested references recursively.
  • +
+
+
+

Conflict Resolution

+
    +
  • In case of conflicting tokens: +
      +
    • Tokens from modifiers override tokens from base sets.
    • +
    • If multiple modifiers define the same token, the last applied modifier takes precedence.
    • +
    +
  • +
+
+
+

Circular Reference Detection

+
    +
  • Detect any circular references during alias resolution.
  • +
  • If a circular reference is found, throw an error and halt the resolution process.
  • +
+
+
+
+ +
+

Resolver Resolution Example

+
+

Example Resolver json file

+
+

We need to decide if the resolver spec also follows the $name, $values, etc.

+
+
+{
+  "name": "Example Resolver",
+  "sets": [
+    {
+      "name": "foundation",
+      "values": ["foundation.json"]
+    },
+    {
+      "values": ["components/button.json"]
+    }
+  ],
+  "modifiers": [
+    {
+      "name": "theme",
+      "type": "enumerated",
+      "values": [
+        {
+          "name": "light",
+          "values": ["themes/light.json"]
+        },
+        {
+          "name": "dark",
+          "values": ["themes/dark.json"]
+        }
+      ],
+      "meta": {
+        "default": "light",
+        "alias": "theme"
+      }
+    }
+  ]
+}
+        
+

+ The alias property should be renamed to namespace to make its purpose clearer. Additionally, it should be moved out of the generic meta property and included in the formal schema definition. Currently, any arbitrary value could exist associated with alias since it's within the unstructured meta object. Anything that is not 100% discardable should not live in meta. +

+
+
+

Input

+
+{
+  "theme": "dark"
+}
+        
+
+
+

Token Files

+

foundation.json

+
+{
+  "color": {
+    "brand": {
+      "primary": {
+        "$value": "#FF0000",
+        "$type": "color"
+      }
+    }
+  }
+}
+        
+

components/button.json

+
+{
+  "button": {
+    "background": {
+      "$value": "{theme.accent}",
+      "$type": "color"
+    },
+    "padding": {
+      "$value": "8px",
+      "$type": "dimension"
+    }
+  }
+}
+        
+

themes/dark.json

+
+{
+  "accent": {
+    "$value": "#00FF00",
+    "$type": "color"
+  }
+}
+        
+
+
+

Resolution Steps

+
+

Step 1: Input Validation

+
    +
  1. Confirm that "theme" is a defined modifier.
  2. +
  3. Verify that "dark" is a valid value for the "theme" modifier.
  4. +
+
+
+

Step 2: Base Set Flattening

+
    +
  1. Load foundation.json and components/button.json. File paths MUST be resolved relative to the location of the resolver file.
  2. +
+

Merge (flatten) tokens, resulting in:

+
+{
+  "color": {
+    "brand": {
+      "primary": {
+        "$value": "#FF0000",
+        "$type": "color"
+      }
+    }
+  },
+  "button": {
+    "background": {
+      "$value": "{theme.accent}",
+      "$type": "color"
+    },
+    "padding": {
+      "$value": "8px",
+      "$type": "dimension"
+    }
+  }
+}
+          
+
+
+

Step 3: Modifier Application

+
    +
  1. Apply the "theme" modifier with value "dark".
  2. +
  3. Load themes/dark.json.
  4. +
+

Apply aliasing as per meta.alias ("theme"), resulting in:

+
+{
+  "theme": {
+    "accent": {
+      "$value": "#00FF00",
+      "$type": "color"
+    }
+  }
+}
+          
+
+
+

Step 4: Alias Resolution

+

Alias resolution is performed on the fully merged set of tokens, after all base sets and modifiers have been applied. This allows for aliases to reference tokens from any loaded file.

+
    +
  • Resolve {theme.accent} in button.background.
  • +
  • Replace with #00FF00.
  • +
+
+
+

Step 5: Conflict Resolution

+
    +
  • No conflicting tokens in this example.
  • +
+
+
+

Step 6: Final Output

+
+{
+  "color": {
+    "brand": {
+      "primary": {
+        "$value": "#FF0000",
+        "$type": "color"
+      }
+    }
+  },
+  "button": {
+    "background": {
+      "$value": "#00FF00",
+      "$type": "color"
+    },
+    "padding": {
+      "$value": "8px",
+      "$type": "dimension"
+    }
+  }
+}
+          
+
+
+
+ +
+

Use Cases

+
+

Style Dictionary Integration

+

The Resolver Specification can be integrated with tools like Style Dictionary to automate the generation of platform-specific design tokens.

+

Example:

+ +
+
+

Real-World Scenario: Theming with Multiple Dimensions

+

In large design systems, you might have multiple brands, each with light and dark themes, and accessibility modes (e.g., high contrast).

+

Implementation:

+ +
+
+ +
+

Orthogonality

+

Modifiers are considered orthogonal when they can be changed independently without affecting each other's resolution logic.

+

Example of Orthogonal Modifiers:

+ +

You can mix any theme with any brand, resulting in all possible combinations.

+

Example of Non-Orthogonal Modifiers:

+ +

In such cases, the resolver must handle dependencies between modifiers, potentially by validating acceptable combinations or structuring modifiers to reflect the dependencies.

+

+ This requires input from the resolver author to explicitly declare whether a modifier is purely orthogonal or not. This declaration should be upfront in the specification, otherwise lazy resolution cannot be supported without the resolver having to check the actual tokens in scope. +

+
+ +
+

Future Extensions

+

The Resolver Specification can be extended to handle more complex scenarios:

+ +
+ +
+

Conformance

+

Tools implementing the Resolver Specification MUST:

+ +
+ +
+

Acknowledgments

+

We thank the members of the Design Tokens Community Group for their contributions and feedback, including:

+ +
+ +
+

References

+
+
Design Tokens Format Specification
+
https://github.com/design-tokens/community-group
+
RFC 2119
+
https://www.ietf.org/rfc/rfc2119.txt
+
Style Dictionary
+
https://amzn.github.io/style-dictionary/
+
Tokens Studio Plugin
+
https://github.com/tokens-studio/figma-plugin
+
+
+ +
+

Additional Context and Information

+

This section is non-normative and intended for early reviewers.

+
+

Resolution Aliasing

+

Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts.

+

Example:

+

Given a token set size.json:

+
+{
+  "sm": {
+    "value": "1px",
+    "type": "dimension"
+  },
+  "lg": {
+    "value": "10px",
+    "type": "dimension"
+  }
+}
+        
+

By applying an alias in the modifier's meta.alias, we can namespace these tokens:

+
+{
+  "modifiers": [
+    {
+      "name": "size",
+      "type": "include",
+      "values": [
+        {
+          "name": "default",
+          "values": ["size.json"]
+        }
+      ],
+      "meta": {
+        "alias": "spacing"
+      }
+    }
+  ]
+}
+        
+

Resulting in tokens accessible via spacing.sm and spacing.lg.

+
+ +
+

Real-World Use Case: GitHub Primer

+

The GitHub Primer design system uses multiple dimensions, including themes and visual modes (e.g., colorblind modes). The Resolver Specification can represent these dimensions as modifiers, allowing for efficient resolution and management of tokens.

+
+

+ Question: Would it be worth to highlight some public design systems and how they would use the resolver spec for more relatable use cases? +

+

+ While public design system examples could be valuable, generic use cases showing common dimensional patterns might be more important as foundational examples: single dimension (1 brand), two dimensions (1 brand + 2 themes), three dimensions (2 brands + 2 themes each), etc. These generic patterns would be the "meat and potatoes" compared to specific design system examples being the "cherry on the cake." +

+
+

Orthogonality Considerations

+

In scenarios where modifiers are not orthogonal, the resolver may need to enforce acceptable combinations and handle dependencies between modifiers. This can be achieved by:

+ +
+
+
+

Example 1: Brand Theming

+ +

Scenario: A company has multiple brands—Brand A and Brand B. Each brand has its own color palette and typography. Components like buttons and headers need to adapt based on the selected brand.

+

+ These examples could be moved to the Resolver Spec netlify app for better presentation. If it uses Astro, interactive code tabs could be added to make the examples more engaging and easier to understand through hands-on exploration. +

+
+

Resolver Definition

+
+{
+  "name": "Brand Theming Resolver",
+  "sets": [
+    {
+      "name": "base",
+      "values": ["tokens/base.json"]
+    },
+    {
+      "values": ["tokens/components.json"]
+    }
+  ],
+  "modifiers": [
+    {
+      "name": "brand",
+      "type": "enumerated",
+      "values": [
+        {
+          "name": "brandA",
+          "values": ["tokens/brands/brandA.json"]
+        },
+        {
+          "name": "brandB",
+          "values": ["tokens/brands/brandB.json"]
+        }
+      ],
+      "meta": {
+        "default": "brandA",
+        "alias": "brand"
+      }
+    }
+  ]
+}
+        
+
+
+

Token Files

+

tokens/base.json

+
+{
+  "color": {
+    "text": {
+      "primary": {
+        "value": "#000000",
+        "type": "color"
+      }
+    }
+  },
+  "font": {
+    "family": {
+      "default": {
+        "value": "Arial, sans-serif",
+        "type": "font"
+      }
+    }
+  }
+}
+        
+

tokens/components.json

+
+{
+  "button": {
+    "background": {
+      "value": "{brand.color.primary}",
+      "type": "color"
+    },
+    "fontFamily": {
+      "value": "{brand.font.family}",
+      "type": "font"
+    }
+  },
+  "header": {
+    "color": {
+      "value": "{brand.color.secondary}",
+      "type": "color"
+    }
+  }
+}
+        
+

tokens/brands/brandA.json

+
+{
+  "color": {
+    "primary": {
+      "value": "#FF5733",
+      "type": "color"
+    },
+    "secondary": {
+      "value": "#C70039",
+      "type": "color"
+    }
+  },
+  "font": {
+    "family": {
+      "value": "'Helvetica Neue', sans-serif",
+      "type": "font"
+    }
+  }
+}
+        
+

tokens/brands/brandB.json

+
+{
+  "color": {
+    "primary": {
+      "value": "#1F618D",
+      "type": "color"
+    },
+    "secondary": {
+      "value": "#2874A6",
+      "type": "color"
+    }
+  },
+  "font": {
+    "family": {
+      "value": "'Times New Roman', serif",
+      "type": "font"
+    }
+  }
+}
+        
+
+
+

Input

+
+{
+  "brand": "brandB"
+}
+        
+
+
+

Resolution Steps

+

Input Validation

+ +

Base Set Flattening

+

Load tokens/base.json and tokens/components.json.

+

Merge tokens:

+
+{
+  "color": {
+    "text": {
+      "primary": {
+        "value": "#000000",
+        "type": "color"
+      }
+    }
+  },
+  "font": {
+    "family": {
+      "default": {
+        "value": "Arial, sans-serif",
+        "type": "font"
+      }
+    }
+  },
+  "button": {
+    "background": {
+      "value": "{brand.color.primary}",
+      "type": "color"
+    },
+    "fontFamily": {
+      "value": "{brand.font.family}",
+      "type": "font"
+    }
+  },
+  "header": {
+    "color": {
+      "value": "{brand.color.secondary}",
+      "type": "color"
+    }
+  }
+}
+        
+

Modifier Application

+

Apply the "brand" modifier with value "brandB".

+

Load tokens/brands/brandB.json.

+

Apply aliasing as per meta.alias ("brand"), resulting in:

+
+{
+  "brand": {
+    "color": {
+      "primary": {
+        "value": "#1F618D",
+        "type": "color"
+      },
+      "secondary": {
+        "value": "#2874A6",
+        "type": "color"
+      }
+    },
+    "font": {
+      "family": {
+        "value": "'Times New Roman', serif",
+        "type": "font"
+      }
+    }
+  }
+}
+        
+

Alias Resolution

+ +

Conflict Resolution

+

No conflicts in this example.

+

Final Output

+
+{
+  "color": {
+    "text": {
+      "primary": {
+        "value": "#000000",
+        "type": "color"
+      }
+    }
+  },
+  "font": {
+    "family": {
+      "default": {
+        "value": "Arial, sans-serif",
+        "type": "font"
+      }
+    }
+  },
+  "button": {
+    "background": {
+      "value": "#1F618D",
+      "type": "color"
+    },
+    "fontFamily": {
+      "value": "'Times New Roman', serif",
+      "type": "font"
+    }
+  },
+  "header": {
+    "color": {
+      "value": "#2874A6",
+      "type": "color"
+    }
+  }
+}
+        
+

Explanation:

+ +
+
+ +
+

Example 2: Component States

+

Scenario: A design system includes buttons that change appearance based on their state—default, hover, active, and disabled. We want to manage these state-specific styles using modifiers.

+
+

Resolver Definition

+
+{
+  "name": "Component States Resolver",
+  "sets": [
+    {
+      "values": ["tokens/components/button.json"]
+    }
+  ],
+  "modifiers": [
+    {
+      "name": "state",
+      "type": "enumerated",
+      "values": [
+        {
+          "name": "default",
+          "values": ["tokens/states/default.json"]
+        },
+        {
+          "name": "hover",
+          "values": ["tokens/states/hover.json"]
+        },
+        {
+          "name": "active",
+          "values": ["tokens/states/active.json"]
+        },
+        {
+          "name": "disabled",
+          "values": ["tokens/states/disabled.json"]
+        }
+      ],
+      "meta": {
+        "default": "default"
+      }
+    }
+  ]
+}
+        
+
+
+

Token Files

+

tokens/components/button.json

+
+{
+  "button": {
+    "background": {
+      "value": "{state.background}",
+      "type": "color"
+    },
+    "textColor": {
+      "value": "{state.textColor}",
+      "type": "color"
+    },
+    "borderColor": {
+      "value": "{state.borderColor}",
+      "type": "color"
+    }
+  }
+}
+        
+

tokens/states/default.json

+
+{
+  "background": {
+    "value": "#FFFFFF",
+    "type": "color"
+  },
+  "textColor": {
+    "value": "#000000",
+    "type": "color"
+  },
+  "borderColor": {
+    "value": "#CCCCCC",
+    "type": "color"
+  }
+}
+        
+

tokens/states/hover.json

+
+{
+  "background": {
+    "value": "#F0F0F0",
+    "type": "color"
+  },
+  "textColor": {
+    "value": "#000000",
+    "type": "color"
+  },
+  "borderColor": {
+    "value": "#BBBBBB",
+    "type": "color"
+  }
+}
+        
+

tokens/states/active.json

+
+{
+  "background": {
+    "value": "#E0E0E0",
+    "type": "color"
+  },
+  "textColor": {
+    "value": "#000000",
+    "type": "color"
+  },
+  "borderColor": {
+    "value": "#AAAAAA",
+    "type": "color"
+  }
+}
+        
+

tokens/states/disabled.json

+
+{
+  "background": {
+    "value": "#F9F9F9",
+    "type": "color"
+  },
+  "textColor": {
+    "value": "#777777",
+    "type": "color"
+  },
+  "borderColor": {
+    "value": "#DDDDDD",
+    "type": "color"
+  }
+}
+        
+
+
+

Input

+
+{
+  "state": "hover"
+}
+        
+
+
+

Resolution Steps

+

Input Validation

+ +

Base Set Flattening

+

Load tokens/components/button.json.

+

Tokens:

+
+{
+  "button": {
+    "background": {
+      "value": "{state.background}",
+      "type": "color"
+    },
+    "textColor": {
+      "value": "{state.textColor}",
+      "type": "color"
+    },
+    "borderColor": {
+      "value": "{state.borderColor}",
+      "type": "color"
+    }
+  }
+}
+        
+

Modifier Application

+

Apply the "state" modifier with value "hover".

+

Load tokens/states/hover.json.

+

Tokens under the "state" namespace:

+
+{
+  "state": {
+    "background": {
+      "value": "#F0F0F0",
+      "type": "color"
+    },
+    "textColor": {
+      "value": "#000000",
+      "type": "color"
+    },
+    "borderColor": {
+      "value": "#BBBBBB",
+      "type": "color"
+    }
+  }
+}
+        
+

Alias Resolution

+ +

Conflict Resolution

+

No conflicts in this example.

+

Final Output

+
+{
+  "button": {
+    "background": {
+      "value": "#F0F0F0",
+      "type": "color"
+    },
+    "textColor": {
+      "value": "#000000",
+      "type": "color"
+    },
+    "borderColor": {
+      "value": "#BBBBBB",
+      "type": "color"
+    }
+  }
+}
+        
+

Explanation:

+ +
+
+ +
+

Example 3: Responsive Design

+

Scenario: A design system needs to support responsive design by adjusting spacing and typography based on screen sizes—mobile, tablet, and desktop. We want to manage these variations using modifiers.

+
+

Resolver Definition

+
+{
+  "name": "Responsive Design Resolver",
+  "sets": [
+    {
+      "name": "core",
+      "values": ["tokens/core.json"]
+    }
+  ],
+  "modifiers": [
+    {
+      "name": "screenSize",
+      "type": "enumerated",
+      "values": [
+        {
+          "name": "mobile",
+          "values": ["tokens/screens/mobile.json"]
+        },
+        {
+          "name": "tablet",
+          "values": ["tokens/screens/tablet.json"]
+        },
+        {
+          "name": "desktop",
+          "values": ["tokens/screens/desktop.json"]
+        }
+      ],
+      "meta": {
+        "default": "mobile",
+        "alias": "screen"
+      }
+    }
+  ]
+}
+        
+
+
+

Token Files

+

tokens/core.json

+
+{
+  "spacing": {
+    "small": {
+      "value": "{screen.spacing.small}",
+      "type": "dimension"
+    },
+    "medium": {
+      "value": "{screen.spacing.medium}",
+      "type": "dimension"
+    },
+    "large": {
+      "value": "{screen.spacing.large}",
+      "type": "dimension"
+    }
+  },
+  "typography": {
+    "fontSize": {
+      "value": "{screen.typography.fontSize}",
+      "type": "dimension"
+    }
+  }
+}
+        
+

tokens/screens/mobile.json

+
+{
+  "spacing": {
+    "small": {
+      "value": "4px",
+      "type": "dimension"
+    },
+    "medium": {
+      "value": "8px",
+      "type": "dimension"
+    },
+    "large": {
+      "value": "12px",
+      "type": "dimension"
+    }
+  },
+  "typography": {
+    "fontSize": {
+      "value": "14px",
+      "type": "dimension"
+    }
+  }
+}
+        
+

tokens/screens/tablet.json

+
+{
+  "spacing": {
+    "small": {
+      "value": "6px",
+      "type": "dimension"
+    },
+    "medium": {
+      "value": "12px",
+      "type": "dimension"
+    },
+    "large": {
+      "value": "18px",
+      "type": "dimension"
+    }
+  },
+  "typography": {
+    "fontSize": {
+      "value": "16px",
+      "type": "dimension"
+    }
+  }
+}
+        
+

tokens/screens/desktop.json

+
+{
+  "spacing": {
+    "small": {
+      "value": "8px",
+      "type": "dimension"
+    },
+    "medium": {
+      "value": "16px",
+      "type": "dimension"
+    },
+    "large": {
+      "value": "24px",
+      "type": "dimension"
+    }
+  },
+  "typography": {
+    "fontSize": {
+      "value": "18px",
+      "type": "dimension"
+    }
+  }
+}
+        
+
+
+

Input

+
+{
+  "screenSize": "desktop"
+}
+        
+
+
+

Resolution Steps

+

Input Validation

+ +

Base Set Flattening

+

Load tokens/core.json.

+

Tokens:

+
+{
+  "spacing": {
+    "small": {
+      "value": "{screen.spacing.small}",
+      "type": "dimension"
+    },
+    "medium": {
+      "value": "{screen.spacing.medium}",
+      "type": "dimension"
+    },
+    "large": {
+      "value": "{screen.spacing.large}",
+      "type": "dimension"
+    }
+  },
+  "typography": {
+    "fontSize": {
+      "value": "{screen.typography.fontSize}",
+      "type": "dimension"
+    }
+  }
+}
+        
+

Modifier Application

+

Apply the "screenSize" modifier with value "desktop".

+

Load tokens/screens/desktop.json.

+

Apply aliasing as per meta.alias ("screen"), resulting in:

+
+{
+  "screen": {
+    "spacing": {
+      "small": {
+        "value": "8px",
+        "type": "dimension"
+      },
+      "medium": {
+        "value": "16px",
+        "type": "dimension"
+      },
+      "large": {
+        "value": "24px",
+        "type": "dimension"
+      }
+    },
+    "typography": {
+      "fontSize": {
+        "value": "18px",
+        "type": "dimension"
+      }
+    }
+  }
+}
+        
+

Alias Resolution

+ +

Conflict Resolution

+

No conflicts in this example.

+

Final Output

+
+{
+  "spacing": {
+    "small": {
+      "value": "8px",
+      "type": "dimension"
+    },
+    "medium": {
+      "value": "16px",
+      "type": "dimension"
+    },
+    "large": {
+      "value": "24px",
+      "type": "dimension"
+    }
+  },
+  "typography": {
+    "fontSize": {
+      "value": "18px",
+      "type": "dimension"
+    }
+  }
+}
+        
+

Explanation:

+ +
+
+ + + \ No newline at end of file From 15b0f3d74bf68b62aaa2d71c6a67735580b779b2 Mon Sep 17 00:00:00 2001 From: Kaelig Deloumeau-Prigent Date: Wed, 30 Jul 2025 18:50:32 -0700 Subject: [PATCH 2/9] Add resolver build and validation steps --- technical-reports/package.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/technical-reports/package.json b/technical-reports/package.json index f99173a..9a1723c 100644 --- a/technical-reports/package.json +++ b/technical-reports/package.json @@ -9,16 +9,18 @@ "directory": "technical-reports" }, "scripts": { - "build": "pnpm run build:index && pnpm run build:format && pnpm run build:color", + "build": "pnpm run build:index && pnpm run build:format && pnpm run build:color && pnpm run build:resolver", "build:index": "mkdir -p ../www/TR/drafts && respec index.html ../www/TR/drafts/index.html --localhost --disable-sandbox", "build:format": "mkdir -p ../www/TR/drafts && mkdir -p ../www/TR/drafts/format && respec format/index.html ../www/TR/drafts/format/index.html --localhost --disable-sandbox", "build:color": "mkdir -p ../www/TR/drafts && mkdir -p ../www/TR/drafts/color && respec color/index.html ../www/TR/drafts/color/index.html --localhost --disable-sandbox", + "build:resolver": "mkdir -p ../www/TR/drafts && mkdir -p ../www/TR/drafts/resolver && respec resolver/index.html ../www/TR/drafts/resolver/index.html --localhost --disable-sandbox", "dev": "chokidar \"**/*.{html,md}\" -c \"pnpm run build\" -d 2000", "lint": "prettier . --check", "format": "prettier . --format --write", - "validate": "pnpm run validate:index && pnpm run validate:format", + "validate": "pnpm run validate:index && pnpm run validate:format && pnpm run validate:resolver", "validate:index": "respec --localhost index.html --haltonerror --haltonwarn", - "validate:format": "respec --localhost format/index.html --haltonerror --haltonwarn" + "validate:format": "respec --localhost format/index.html --haltonerror --haltonwarn", + "validate:resolver": "respec --localhost resolver/index.html --haltonerror --haltonwarn" }, "devDependencies": { "chokidar": "^4.0.3", From 632c5aef2a668cee94440bd549992998e75e9fb6 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Sun, 3 Aug 2025 20:24:15 -0600 Subject: [PATCH 3/9] chore: Format resolver spec into individual markdown files (#290) * chore: format resolver module into markdown * chore: run Prettier on CHANGELOG.md --- technical-reports/format/index.html | 1 - technical-reports/index.html | 2 +- technical-reports/resolver/CHANGELOG.md | 42 +- .../additional-context-and-information.md | 78 + technical-reports/resolver/conformance.md | 7 + technical-reports/resolver/example1.md | 290 +++ technical-reports/resolver/example2.md | 253 +++ technical-reports/resolver/example3.md | 283 +++ technical-reports/resolver/file-format.md | 127 ++ .../resolver/future-extensions.md | 7 + technical-reports/resolver/index.html | 1740 ++--------------- technical-reports/resolver/introduction.md | 10 + technical-reports/resolver/orthogonality.md | 26 + technical-reports/resolver/rationale.md | 48 + .../resolver/resolution-aliasing.md | 155 ++ .../resolver/resolution-example.md | 199 ++ .../resolver/resolution-logic.md | 94 + technical-reports/resolver/terminology.md | 103 + technical-reports/resolver/use-cases.md | 25 + 19 files changed, 1874 insertions(+), 1616 deletions(-) create mode 100644 technical-reports/resolver/additional-context-and-information.md create mode 100644 technical-reports/resolver/conformance.md create mode 100644 technical-reports/resolver/example1.md create mode 100644 technical-reports/resolver/example2.md create mode 100644 technical-reports/resolver/example3.md create mode 100644 technical-reports/resolver/file-format.md create mode 100644 technical-reports/resolver/future-extensions.md create mode 100644 technical-reports/resolver/introduction.md create mode 100644 technical-reports/resolver/orthogonality.md create mode 100644 technical-reports/resolver/rationale.md create mode 100644 technical-reports/resolver/resolution-aliasing.md create mode 100644 technical-reports/resolver/resolution-example.md create mode 100644 technical-reports/resolver/resolution-logic.md create mode 100644 technical-reports/resolver/terminology.md create mode 100644 technical-reports/resolver/use-cases.md diff --git a/technical-reports/format/index.html b/technical-reports/format/index.html index 8c5a7e0..530b267 100644 --- a/technical-reports/format/index.html +++ b/technical-reports/format/index.html @@ -34,7 +34,6 @@ { name: 'Matthew Ström', url: 'https://matthewstrom.com' }, { name: 'Mike Kamminga', url: 'https://x.com/mikekamminga' }, ], - github: { repoURL: 'https://github.com/design-tokens/community-group', branch: 'main', diff --git a/technical-reports/index.html b/technical-reports/index.html index c555d62..dd6fb73 100644 --- a/technical-reports/index.html +++ b/technical-reports/index.html @@ -72,7 +72,7 @@

Modules

Format
  • Color
  • -
  • Animations (coming soon)
  • +
  • Resolver
  • + + + + + + + +## Orthogonality Considerations + +In scenarios where modifiers are not orthogonal, the resolver may need to enforce acceptable combinations and handle dependencies between modifiers. This can be achieved by: + +- Defining valid combinations explicitly. +- Using nested modifiers or modifier groups. +- Providing validation logic to prevent invalid input combinations. diff --git a/technical-reports/resolver/conformance.md b/technical-reports/resolver/conformance.md new file mode 100644 index 0000000..71eb6be --- /dev/null +++ b/technical-reports/resolver/conformance.md @@ -0,0 +1,7 @@ +Tools implementing the Resolver Specification MUST: + +- **Support the Resolution Process**: Implement the resolution logic as defined, including input validation, base set flattening, modifier application, aliasing, and conflict resolution. +- **Validate Inputs**: Ensure that provided modifier inputs match the defined modifiers and acceptable values. +- **Resolve Aliases Correctly**: Handle token references accurately, including recursive references and detection of circular dependencies. +- **Preserve Token Properties**: Maintain additional token properties (e.g., description, type) throughout the resolution process. +- **Handle Errors Gracefully**: Provide meaningful error messages for issues like invalid inputs or circular references. diff --git a/technical-reports/resolver/example1.md b/technical-reports/resolver/example1.md new file mode 100644 index 0000000..017020c --- /dev/null +++ b/technical-reports/resolver/example1.md @@ -0,0 +1,290 @@ +# Example 1: Brand Theming + + diff --git a/technical-reports/resolver/example2.md b/technical-reports/resolver/example2.md new file mode 100644 index 0000000..4cccbc4 --- /dev/null +++ b/technical-reports/resolver/example2.md @@ -0,0 +1,253 @@ +# Example 2: Component States + + diff --git a/technical-reports/resolver/example3.md b/technical-reports/resolver/example3.md new file mode 100644 index 0000000..fe03001 --- /dev/null +++ b/technical-reports/resolver/example3.md @@ -0,0 +1,283 @@ +# Example 3: Responsive Design + + diff --git a/technical-reports/resolver/file-format.md b/technical-reports/resolver/file-format.md new file mode 100644 index 0000000..e636521 --- /dev/null +++ b/technical-reports/resolver/file-format.md @@ -0,0 +1,127 @@ +# File Format + +A resolver is defined as a JSON object with the following properties: + +- **name** (optional): A human-readable name for the resolver. +- **description** (optional): A description of the resolver's purpose. +- **sets** (required): An array of token sets to be used as the base for resolution. +- **modifiers** (optional): An array of modifiers that can alter or override tokens from the base sets. + +### Token Sets + +Each token set in the **sets** array is an object with the following properties: + +- **name** (optional): An identifier for the set. +- **values** (required): An array of references to token files or inline token definitions. A reference MUST be a string containing a path to a token file. An inline token definition MUST be a JSON object containing a valid design token structure. +- **meta** (optional): Additional metadata, such as proprietary extensions. + + + + + +### Modifiers + +Modifiers are defined in the **modifiers** array and can have different types, affecting how they influence the resolution +process. + +Each modifier is an object with the following properties: + +- **name** (required): The name of the modifier. +- **type** (optional, default: "enumerated"): The type of modifier. This can be "enumerated" or "include". +- **values** (required): An array of possible values for the modifier. +- **meta** (optional): Additional metadata, such as default values or aliases. + + + + + + + + + + diff --git a/technical-reports/resolver/future-extensions.md b/technical-reports/resolver/future-extensions.md new file mode 100644 index 0000000..8ef6d97 --- /dev/null +++ b/technical-reports/resolver/future-extensions.md @@ -0,0 +1,7 @@ +# Future Extensions + +The Resolver Specification can be extended to handle more complex scenarios: + +- **Conditional Modifiers**: Modifiers that are applied based on conditions or contexts not specified directly in the input. +- **Dynamic Resolution**: Support for runtime evaluation of tokens based on environmental factors (e.g., screen size, user preferences). +- **Dependency Graphs**: Explicitly defining dependencies between modifiers to handle non-orthogonal scenarios. diff --git a/technical-reports/resolver/index.html b/technical-reports/resolver/index.html index 0637abe..91634af 100644 --- a/technical-reports/resolver/index.html +++ b/technical-reports/resolver/index.html @@ -1,1627 +1,181 @@ - + Design Tokens Resolver Module - +

    - This document describes the technical specification for a resolver mechanism used to manage and resolve design tokens in complex scenarios involving themes, modes, brands, and other modifiers. It extends the Design Tokens Format Specification by introducing a structured way to combine and override token sets to produce a final, resolved set of tokens for consumption by design tools and platforms. + This document describes the technical specification for a resolver + mechanism used to manage and resolve design tokens in complex scenarios + involving themes, modes, brands, and other modifiers. It extends the + Design Tokens Format Specification by introducing a structured way to + combine and override token sets to produce a final, resolved set of + tokens for consumption by design tools and platforms.

    - This is a snapshot of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C or the Design Tokens W3C Community Group Membership. Don’t cite this document other than as work in progress. -

    -

    - This document has been published to facilitate Wide Review. + This is a snapshot of the editors’ draft. It is provided for discussion + only and may change at any moment. Its publication here does not imply + endorsement of its contents by W3C or the Design Tokens W3C Community + Group Membership. Don’t cite this document other than as work in + progress.

    +

    This document has been published to facilitate Wide Review.

    - This document was produced by the Design Tokens W3C Community Group, and contributions to this draft are governed by Community Contributor License Agreement (CLA), as specified by the W3C Community Group Process. + This document was produced by the Design Tokens W3C Community Group, and + contributions to this draft are governed by + Community Contributor License Agreement (CLA), as specified by the + W3C Community Group Process.

    -
    -

    Introduction

    -

    - Design tokens are key-value pairs that represent design decisions, allowing for consistent use across different platforms and tools. In complex design systems, there is a need to manage multiple dimensions such as themes, modes (e.g., light and dark), brands, and variants. This introduces challenges in organizing tokens, resolving references, and applying overrides. -

    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    Acknowledgments

    - The Resolver Specification addresses these challenges by introducing a structured mechanism to: -

    -
      -
    • Organize tokens into sets and modifiers.
    • -
    • Define how tokens are resolved when multiple sets and modifiers are involved.
    • -
    • Support scalability by handling arbitrary dimensions without complicating the core token definitions.
    • -
    • Maintain modularity and reusability by allowing tokens to be combined and overridden in a controlled manner.
    • -
    -
    - -
    -

    Rationale

    -
    -

    Context and Problem Statement

    -

    In design systems, especially those at scale, managing multiple themes, modes, brands, and other modifiers can become complex. Designers and developers often struggle with:

    -
      -
    • Naming Conflicts: Using complex naming conventions to differentiate tokens across different themes or modes, leading to brittle and hard-to-maintain token names.
    • -
    • Scalability Issues: Difficulty in scaling token management when new dimensions are added, such as additional themes or brands.
    • -
    • Tooling Limitations: Existing tools may not support multi-dimensional token resolution, forcing teams to create workarounds that complicate their workflows.
    • -
    • Separation of Concerns: Embedding resolution logic within token files mixes data with behavior, violating separation of concerns.
    • -
    -
    -
    -

    Parameters

    -
      -
    • Simplicity: The solution should keep token files simple and editable by hand, without requiring complex tooling.
    • -
    • Scalability: It should support arbitrary dimensions (e.g., themes, modes, brands) without significant overhead.
    • -
    • Modularity: Tokens should be organized in a way that allows for modularity and reusability.
    • -
    • Separation of Concerns: Resolution logic should be externalized from token definitions.
    • -
    -
    -
    -

    Background

    -

    Existing approaches, such as embedding modifiers directly into token names or using global theme files, have limitations. They often lead to:

    -
      -
    • Global Namespace Pollution: All tokens exist in a single global namespace, increasing the likelihood of naming conflicts.
    • -
    • High Cognitive Load: Designers and developers must remember complex naming conventions and how different tokens relate across dimensions.
    • -
    • Performance Issues: Resolving tokens in systems with many dimensions can be slow due to the combinatorial explosion of permutations.
    • -
    -
    -
    -

    Motivation

    -

    The Resolver Specification aims to:

    -
      -
    • Simplify Token Management: By externalizing resolution logic, token files remain simple and focused on defining values.
    • -
    • Enhance Modularity: Tokens can be organized into sets and modifiers, allowing teams to work on isolated components or themes without affecting others.
    • -
    • Improve Performance: By reducing the scope of tokens in resolution, the process becomes faster and more efficient.
    • -

      - The term "process" needs clarification - what specific process is being referred to? Is this the "resolution process of the resolver"? More explanation is needed to understand exactly what becomes faster and more efficient when token scope is reduced during resolution. -

      -
    • Facilitate Tool Integration: Providing a standard way to define resolution logic makes it easier for tools to integrate and automate token management.
    • -
    -
    -
    - -

    - A side effect of using a resolver should be the ability to have a clear dependency graph of how a resolution request would occur purely based on the inputs. This would enable better understanding of modifier interactions and support more efficient resolution strategies. -

    - -
    -

    Resolver Terminology

    -
    -
    Resolver
    -
    A mechanism that combines token sets and applies modifiers to produce a final, resolved set of design tokens.
    -
    Token Set
    -
    A collection of design tokens grouped together, often representing foundational tokens, component-specific tokens, or theme-specific tokens.
    -
    Dimension
    -
    Dimensions are categories used to organize your design tokens. See them as contexts in which token values might change:
    -
      -
    • Brands
    • -
    • Surface
    • -
    • Language direction
    • -
    • Themes
    • -
    • Platform
    • -
    • Screen size
    • -
    • Density
    • -
    • Component
    • -
    • State
    • -
    • Variant
    • -
    • Contrast
    • -
    -

    - The term "Dimension" is potentially confusing since "Dimension" is also a type of token and a general concept. Consider using "Context" instead, which aligns with the existing definition as "contexts in which token values might change." Additionally, explicit mention of color schemes may be problematic - developers often distinguish between "themes" and "color modes" (like Windows High Contrast mode for accessibility), and it's unclear whether everyone would understand themes to encompass dark/light/high-contrast modes. -

    -
    Modifier
    -
    - An entity that modifies or overrides tokens in the base sets. Modifiers can represent dimensions like themes, modes, brands, or any other contextual variations. -
    -
    Enumerated Modifier
    -
    A modifier with predefined, named values (e.g., "light", "dark" for a theme modifier).
    -
    Include Modifier
    -
    A modifier that replaces or includes entire token sets during resolution.
    -
    Alias
    -
    An optional property that allows for namespacing or renaming token paths during resolution.
    -
    -

    - The modifier types and their behaviors aren't clear from these definitions alone. Real-world examples showing how Enumerated Modifiers, Include Modifiers, and Alias properties work in practice would help clarify their purpose and usage. -

    -
    -
    Input
    -
    Parameters provided to the resolver to specify which modifiers to apply during the resolution process.
    -
    Resolution
    -
    The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens.
    -

    - The specification should address different real-world resolution use cases and their performance implications: full upfront resolution (resolving all possible combinations ahead of time), lazy resolution (resolving on-demand), and partial resolution for complex components. Without being upfront about token combinations and scopes, systems face combinatorial explosion problems that make resolution inefficient. There may be significant performance aspects to consider, especially for JIT (Just-In-Time) resolution scenarios. Input from tool makers like Figma and other design platforms would be valuable to understand real-world performance requirements and constraints. -

    -
    Orthogonality
    -
    The property of modifiers being independent of each other, allowing them to be combined freely without affecting each other's resolution logic.
    -
    -
    - -
    -

    Resolver File Format

    -

    A resolver is defined as a JSON object with the following properties:

    -
      -
    • name (optional): A human-readable name for the resolver.
    • -
    • description (optional): A description of the resolver's purpose.
    • -
    • sets (required): An array of token sets to be used as the base for resolution.
    • -
    • modifiers (optional): An array of modifiers that can alter or override tokens from the base sets.
    • -
    - -
    -

    Token Sets

    -

    Each token set in the sets array is an object with the following properties:

    -
      -
    • name (optional): An identifier for the set.
    • -
    • values (required): An array of references to token files or inline token definitions. A reference MUST be a string containing a path to a token file. An inline token definition MUST be a JSON object containing a valid design token structure.
    • -
    • meta (optional): Additional metadata, such as proprietary extensions.
    • -
    -

    Example:

    -
    -{
    -  "sets": [
    -    {
    -      "name": "foundation",
    -      "values": ["foundation.json"]
    -    },
    -    {
    -      "values": [
    -        "components/button.json",
    -        {
    -          "inline-token": { "$value": "some-value" }
    -        }
    -      ]
    -    }
    -  ]
    -}
    -        
    -

    - It is recommended to use .tokens.json as the file extension for token files to align with the Design Tokens Format Specification naming conventions. This helps reinforce that these files should contain valid DTCG token structures rather than arbitrary JSON data. -

    -
    - -
    -

    Modifiers

    -

    Modifiers are defined in the modifiers array and can have different types, affecting how they influence the resolution process.

    -

    Each modifier is an object with the following properties:

    -
      -
    • name (required): The name of the modifier.
    • -
    • type (optional, default: "enumerated"): The type of modifier. This can be "enumerated" or "include".
    • -
    • values (required): An array of possible values for the modifier.
    • -
    • meta (optional): Additional metadata, such as default values or aliases.
    • -
    -

    - Default values and aliasing should be moved out of the generic meta property into specific typed properties. The meta property should be reserved for implementation-specific extensions rather than functionality that directly contributes to resolution behavior. -

    -

    Example of an "enumerated" modifier:

    -
    -{
    -  "modifiers": [
    -    {
    -      "name": "theme",
    -      "type": "enumerated",
    -      "values": [
    -        {
    -          "name": "light",
    -          "values": ["themes/light.json"]
    -        },
    -        {
    -          "name": "dark",
    -          "values": ["themes/dark.json"]
    -        }
    -      ],
    -      "meta": {
    -        "default": "light",
    -        "alias": "theme"
    -      }
    -    }
    -  ]
    -}
    -        
    -

    - There are structural problems with using arrays for modifiers and their values. Since name is required and must be unique for proper resolution, arrays allow for multiple conflicting values which should be impossible. For example, if a user passes { theme: "dark" } and there are 2 modifiers named "theme" and 2 modifier values named "dark", this creates ambiguity that should be an error rather than defaulting to "take the first one." While arrays make sense for defining order of application, if we want key-value mapping for applying modifiers, flat objects should be required to ensure unique IDs by design. -

    -

    - The specification needs to clarify what constitutes valid values in the values array for modifiers. Key questions include: Are only relative pathnames allowed? Can referenced files have sets/modifiers of their own (enabling resolver chaining)? Should there be support for referencing sets by name (e.g., "foundation") rather than repeating file paths? The concept of chaining resolvers could use different type values to identify other resolvers. Additionally, values could support local references starting with "#" or string references to set names, making it easier to reference entire sets without listing all token file paths. -

    - -

    Example of an "include" modifier. This type of modifier is used to conditionally include a set of tokens. The `values` array for an include modifier contains objects with a `name` and a corresponding list of `values` (file paths or inline tokens) that will be included if that name is present in the input.

    -
    -{
    -  "modifiers": [
    -    {
    -      "name": "features",
    -      "type": "include",
    -      "values": [
    -        {
    -          "name": "experimental-feature-x",
    -          "values": ["features/feature-x.json"]
    -        }
    -      ]
    -    }
    -  ]
    -}
    -        
    -
    -
    - -
    -

    Resolution Aliasing

    -

    Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts.

    -
    -

    Namespace vs Alias Terminology: Should the alias property be renamed to namespace to avoid confusion with token aliases (references to other tokens)?

    -

    Redundancy with Modifier Names: The meta.alias property may be redundant since modifiers already have a name property that could serve the same namespacing purpose.

    -

    Auto-namespacing Concerns: The automatic application of namespacing for modifiers introduces significant complexity and potential issues:

    -
      -
    • Requires pre-resolution conflict checking across all sets and modifiers
    • -
    • Modifier JSON must be complete sets to avoid undefined token references
    • -
    • Risk of infinite loops if modifiers reference tokens in other modifier namespaces
    • -
    • Backwards compatibility issues requiring extensive token renames in existing design systems
    • -
    -

    Alternative Approach: Consider removing auto-namespacing for modifiers and using shared merging logic between sets and modifiers for better compatibility and simplicity.

    -
    -

    Example:

    -

    Given a token set size.json:

    -
    -{
    -  "sm": {
    -    "value": "1px",
    -    "type": "dimension"
    -  },
    -  "lg": {
    -    "value": "10px",
    -    "type": "dimension"
    -  }
    -}
    -      
    -

    By applying an alias in the modifier's meta.alias, we can namespace these tokens:

    -
    -{
    -  "modifiers": [
    -    {
    -      "name": "size",
    -      "type": "include",
    -      "values": [
    -        {
    -          "name": "default",
    -          "values": ["size.json"]
    -        }
    -      ],
    -      "meta": {
    -        "alias": "spacing"
    -      }
    -    }
    -  ]
    -}
    -      
    -

    Resulting in tokens accessible via spacing.sm and spacing.lg.

    -

    - If the meta.alias behavior described above is normative/required behavior, it should not be part of the generic meta property but should be defined as part of the formal schema. Alternatively, if this is just an example of tooling-specific behavior, it should be clearly called out as such and not presented as part of the core specification. -

    - -
    -

    JSON Schema

    -

    Source: https://resolver-spec.netlify.app/reference/schema/

    -
    -{
    -  "$id": "https://schemas.tokens.studio/prototype/resolver.json",
    -  "$schema": "https://json-schema.org/draft/2020-12/schema",
    -  "description": "Resolver Specification",
    -  "$defs": {
    -    "tokenSet": {
    -      "type": "object",
    -      "properties": {
    -        "name": {
    -          "type": "string"
    -        },
    -        "values": {
    -          "type": "array",
    -          "items": {
    -            "type": "string"
    -          }
    -        }
    -      },
    -      "required": ["values"]
    -    },
    -    "modifier": {
    -      "type": "object",
    -      "properties": {
    -        "name": {
    -          "type": "string"
    -        },
    -        "values": {
    -          "type": "array",
    -          "items": {
    -            "type": "object",
    -            "properties": {
    -              "name": {
    -                "type": "string"
    -              },
    -              "values": {
    -                "type": "array",
    -                "items": {
    -                  "type": "string"
    -                }
    -              }
    -            },
    -            "required": ["name", "values"]
    -          }
    -        },
    -        "meta": {
    -          "type": "object",
    -          "additionalProperties": true
    -        }
    -      },
    -      "required": ["name", "values"]
    -    }
    -  },
    -  "type": "object",
    -  "properties": {
    -    "name": {
    -      "type": "string"
    -    },
    -    "description": {
    -      "type": "string"
    -    },
    -    "sets": {
    -      "type": "array",
    -      "items": {
    -        "$ref": "#/$defs/tokenSet"
    -      }
    -    },
    -    "modifiers": {
    -      "type": "array",
    -      "items": {
    -        "$ref": "#/$defs/modifier"
    -      }
    -    }
    -  },
    -  "required": ["sets", "modifiers"]
    -}
    -         
    -
    -
    - -
    -

    Resolution Logic

    -

    The resolution process involves the following steps:

    -
      -
    1. Input Validation: Ensure the provided inputs match the expected modifiers and their acceptable values.
    2. -

      - The term "inputs" is used throughout the specification but never formally defined. Inputs appear to represent the permutation or combination of modifiers being selected for resolution - essentially the question "What would my output look like given some combination of modifiers being enabled or chosen?" This fundamental concept should be clearly defined, potentially as "data" or with a more specific term that clarifies its role in the resolution process. -

      -
    3. Base Set Flattening: Load and merge the token sets specified in the sets array. Later sets override earlier ones if there are naming conflicts.
    4. -

      - The merging process needs detailed specification to handle DTCG token data correctly. Simply performing a deep merge on raw JSON could yield different results than fully interpreting the DTCG data first and then merging. This is particularly important for inheritable format properties like $type. For example, when loading two JSON files with a top-level "color" key where one has $type: "color" and another has $type: "gradient", these represent incompatible schemas that cannot be merged. The specification must outline what constitutes "mergeable" vs "unmergeable" token sets and provide clear rules for handling such conflicts. -

      -

      - The specification needs clear step-by-step instructions for how deep merging should occur, including detailed guidance on handling edge cases or when to error appropriately. This should include specific algorithms and decision trees for implementers to follow consistently. -

      -
    5. Modifier Application: Apply the selected modifiers based on the inputs. Modifiers can override tokens from the base sets or introduce new tokens.
    6. -

      - The specification should clarify the resolution order when multiple modifiers are applied simultaneously. For instance, if both "theme" and "brand" modifiers are used and both attempt to override the same token, which modifier should take precedence? Clear precedence rules are needed to ensure consistent and predictable resolution behavior across implementations. -

      -
    7. Aliasing and Namespacing: Apply any aliasing specified in the modifiers to namespace or rename tokens.
    8. -

      - The specification needs to clarify the different types of "aliasing" and their behavior during resolution. There are two distinct concepts being referred to as "aliasing": (1) Namespacing aliasing - where tokens are renamed/namespaced (e.g., "red.500" becomes "colors.red.500"), and (2) Token reference aliasing - where one token's value references another token's value. The specification should clearly distinguish between these concepts and address how namespacing aliases behave when tokens are overridden by later sets - specifically, if an alias points to a token that gets overridden, does the alias resolve to the new/overridden value? -

      -
    9. Alias Resolution: Resolve any token references (aliases) using the combined tokens from the base sets and modifiers.
    10. -
    11. Conflict Resolution: In case of conflicting tokens (same name and path), later modifiers or sets override earlier ones.
    12. -
    13. Circular Reference Detection: Detect and handle circular references as errors.
    14. -
    15. Final Output: Produce a flat, resolved set of tokens ready for consumption.
    16. -
    - -
    -

    Detailed Steps

    -
    -

    Input Validation

    -
      -
    • Verify that all provided modifier inputs correspond to defined modifiers.
    • -
    • Check that the input values are among the acceptable options defined in the modifiers.
    • -
    -
    -
    -

    Base Set Flattening

    -
      -
    • Load each token set in the order specified.
    • -
    • Merge the tokens, with later sets overriding earlier ones on name conflicts.
    • -
    -
    -
    -

    Modifier Application

    -
      -
    • For each modifier: -
        -
      • Apply any aliasing or namespacing specified in meta.
      • -
      • Load the token sets associated with the selected modifier value.
      • -
      • Merge these tokens with the base tokens, applying overrides as necessary.
      • -
      -
    • -
    -
    -
    -

    Alias Resolution

    -

    Alias resolution is performed on the fully merged set of tokens, after all base sets and modifiers have been applied. This allows for aliases to reference tokens from any loaded file.

    -
      -
    • Iterate over all tokens to find references (e.g., {theme.accent}).
    • -
    • Resolve references first within the same token set.
    • -
    • If not found, resolve references from the modifiers, following the order of precedence.
    • -
    • Handle nested references recursively.
    • -
    -
    -
    -

    Conflict Resolution

    -
      -
    • In case of conflicting tokens: -
        -
      • Tokens from modifiers override tokens from base sets.
      • -
      • If multiple modifiers define the same token, the last applied modifier takes precedence.
      • -
      -
    • -
    -
    -
    -

    Circular Reference Detection

    -
      -
    • Detect any circular references during alias resolution.
    • -
    • If a circular reference is found, throw an error and halt the resolution process.
    • -
    -
    -
    -
    - -
    -

    Resolver Resolution Example

    -
    -

    Example Resolver json file

    -
    -

    We need to decide if the resolver spec also follows the $name, $values, etc.

    -
    -
    -{
    -  "name": "Example Resolver",
    -  "sets": [
    -    {
    -      "name": "foundation",
    -      "values": ["foundation.json"]
    -    },
    -    {
    -      "values": ["components/button.json"]
    -    }
    -  ],
    -  "modifiers": [
    -    {
    -      "name": "theme",
    -      "type": "enumerated",
    -      "values": [
    -        {
    -          "name": "light",
    -          "values": ["themes/light.json"]
    -        },
    -        {
    -          "name": "dark",
    -          "values": ["themes/dark.json"]
    -        }
    -      ],
    -      "meta": {
    -        "default": "light",
    -        "alias": "theme"
    -      }
    -    }
    -  ]
    -}
    -        
    -

    - The alias property should be renamed to namespace to make its purpose clearer. Additionally, it should be moved out of the generic meta property and included in the formal schema definition. Currently, any arbitrary value could exist associated with alias since it's within the unstructured meta object. Anything that is not 100% discardable should not live in meta. -

    -
    -
    -

    Input

    -
    -{
    -  "theme": "dark"
    -}
    -        
    -
    -
    -

    Token Files

    -

    foundation.json

    -
    -{
    -  "color": {
    -    "brand": {
    -      "primary": {
    -        "$value": "#FF0000",
    -        "$type": "color"
    -      }
    -    }
    -  }
    -}
    -        
    -

    components/button.json

    -
    -{
    -  "button": {
    -    "background": {
    -      "$value": "{theme.accent}",
    -      "$type": "color"
    -    },
    -    "padding": {
    -      "$value": "8px",
    -      "$type": "dimension"
    -    }
    -  }
    -}
    -        
    -

    themes/dark.json

    -
    -{
    -  "accent": {
    -    "$value": "#00FF00",
    -    "$type": "color"
    -  }
    -}
    -        
    -
    -
    -

    Resolution Steps

    -
    -

    Step 1: Input Validation

    -
      -
    1. Confirm that "theme" is a defined modifier.
    2. -
    3. Verify that "dark" is a valid value for the "theme" modifier.
    4. -
    -
    -
    -

    Step 2: Base Set Flattening

    -
      -
    1. Load foundation.json and components/button.json. File paths MUST be resolved relative to the location of the resolver file.
    2. -
    -

    Merge (flatten) tokens, resulting in:

    -
    -{
    -  "color": {
    -    "brand": {
    -      "primary": {
    -        "$value": "#FF0000",
    -        "$type": "color"
    -      }
    -    }
    -  },
    -  "button": {
    -    "background": {
    -      "$value": "{theme.accent}",
    -      "$type": "color"
    -    },
    -    "padding": {
    -      "$value": "8px",
    -      "$type": "dimension"
    -    }
    -  }
    -}
    -          
    -
    -
    -

    Step 3: Modifier Application

    -
      -
    1. Apply the "theme" modifier with value "dark".
    2. -
    3. Load themes/dark.json.
    4. -
    -

    Apply aliasing as per meta.alias ("theme"), resulting in:

    -
    -{
    -  "theme": {
    -    "accent": {
    -      "$value": "#00FF00",
    -      "$type": "color"
    -    }
    -  }
    -}
    -          
    -
    -
    -

    Step 4: Alias Resolution

    -

    Alias resolution is performed on the fully merged set of tokens, after all base sets and modifiers have been applied. This allows for aliases to reference tokens from any loaded file.

    -
      -
    • Resolve {theme.accent} in button.background.
    • -
    • Replace with #00FF00.
    • -
    -
    -
    -

    Step 5: Conflict Resolution

    -
      -
    • No conflicting tokens in this example.
    • -
    -
    -
    -

    Step 6: Final Output

    -
    -{
    -  "color": {
    -    "brand": {
    -      "primary": {
    -        "$value": "#FF0000",
    -        "$type": "color"
    -      }
    -    }
    -  },
    -  "button": {
    -    "background": {
    -      "$value": "#00FF00",
    -      "$type": "color"
    -    },
    -    "padding": {
    -      "$value": "8px",
    -      "$type": "dimension"
    -    }
    -  }
    -}
    -          
    -
    -
    -
    - -
    -

    Use Cases

    -
    -

    Style Dictionary Integration

    -

    The Resolver Specification can be integrated with tools like Style Dictionary to automate the generation of platform-specific design tokens.

    -

    Example:

    -
      -
    • Use the resolver to generate the resolved token set based on the desired modifiers.
    • -
    • Feed the resolved token set into Style Dictionary.
    • -
    • Configure Style Dictionary to output tokens in the desired formats (e.g., CSS variables, Sass variables).
    • -
    -
    -
    -

    Real-World Scenario: Theming with Multiple Dimensions

    -

    In large design systems, you might have multiple brands, each with light and dark themes, and accessibility modes (e.g., high contrast).

    -

    Implementation:

    -
      -
    • Define modifiers for each dimension (e.g., "brand", "theme", "accessibility").
    • -
    • Use the resolver to combine base tokens with the appropriate modifiers.
    • -
    • Ensure that modifiers are orthogonal where possible to simplify the resolution logic.
    • -
    -
    -
    - -
    -

    Orthogonality

    -

    Modifiers are considered orthogonal when they can be changed independently without affecting each other's resolution logic.

    -

    Example of Orthogonal Modifiers:

    -
      -
    • Theme: "light", "dark"
    • -
    • Brand: "brandA", "brandB"
    • -
    -

    You can mix any theme with any brand, resulting in all possible combinations.

    -

    Example of Non-Orthogonal Modifiers:

    -
      -
    • A "theme" modifier that includes a "dimmed" option only available in "dark" mode.
    • -
    -

    In such cases, the resolver must handle dependencies between modifiers, potentially by validating acceptable combinations or structuring modifiers to reflect the dependencies.

    -

    - This requires input from the resolver author to explicitly declare whether a modifier is purely orthogonal or not. This declaration should be upfront in the specification, otherwise lazy resolution cannot be supported without the resolver having to check the actual tokens in scope. + We thank the members of the Design Tokens Community Group for their + contributions and feedback, including:

    -
    - -
    -

    Future Extensions

    -

    The Resolver Specification can be extended to handle more complex scenarios:

    -
      -
    • Conditional Modifiers: Modifiers that are applied based on conditions or contexts not specified directly in the input.
    • -
    • Dynamic Resolution: Support for runtime evaluation of tokens based on environmental factors (e.g., screen size, user preferences).
    • -
    • Dependency Graphs: Explicitly defining dependencies between modifiers to handle non-orthogonal scenarios.
    • -
    -
    - -
    -

    Conformance

    -

    Tools implementing the Resolver Specification MUST:

    -
      -
    • Support the Resolution Process: Implement the resolution logic as defined, including input validation, base set flattening, modifier application, aliasing, and conflict resolution.
    • -
    • Validate Inputs: Ensure that provided modifier inputs match the defined modifiers and acceptable values.
    • -
    • Resolve Aliases Correctly: Handle token references accurately, including recursive references and detection of circular dependencies.
    • -
    • Preserve Token Properties: Maintain additional token properties (e.g., description, type) throughout the resolution process.
    • -
    • Handle Errors Gracefully: Provide meaningful error messages for issues like invalid inputs or circular references.
    • -
    -
    - -
    -

    Acknowledgments

    -

    We thank the members of the Design Tokens Community Group for their contributions and feedback, including:

    • [Contributors' Names]
    -
    -

    References

    -
    -
    Design Tokens Format Specification
    -
    https://github.com/design-tokens/community-group
    -
    RFC 2119
    -
    https://www.ietf.org/rfc/rfc2119.txt
    -
    Style Dictionary
    -
    https://amzn.github.io/style-dictionary/
    -
    Tokens Studio Plugin
    -
    https://github.com/tokens-studio/figma-plugin
    -
    -
    - -
    -

    Additional Context and Information

    -

    This section is non-normative and intended for early reviewers.

    -
    -

    Resolution Aliasing

    -

    Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts.

    -

    Example:

    -

    Given a token set size.json:

    -
    -{
    -  "sm": {
    -    "value": "1px",
    -    "type": "dimension"
    -  },
    -  "lg": {
    -    "value": "10px",
    -    "type": "dimension"
    -  }
    -}
    -        
    -

    By applying an alias in the modifier's meta.alias, we can namespace these tokens:

    -
    -{
    -  "modifiers": [
    -    {
    -      "name": "size",
    -      "type": "include",
    -      "values": [
    -        {
    -          "name": "default",
    -          "values": ["size.json"]
    -        }
    -      ],
    -      "meta": {
    -        "alias": "spacing"
    -      }
    -    }
    -  ]
    -}
    -        
    -

    Resulting in tokens accessible via spacing.sm and spacing.lg.

    -
    - -
    -

    Real-World Use Case: GitHub Primer

    -

    The GitHub Primer design system uses multiple dimensions, including themes and visual modes (e.g., colorblind modes). The Resolver Specification can represent these dimensions as modifiers, allowing for efficient resolution and management of tokens.

    -
    -

    - Question: Would it be worth to highlight some public design systems and how they would use the resolver spec for more relatable use cases? -

    -

    - While public design system examples could be valuable, generic use cases showing common dimensional patterns might be more important as foundational examples: single dimension (1 brand), two dimensions (1 brand + 2 themes), three dimensions (2 brands + 2 themes each), etc. These generic patterns would be the "meat and potatoes" compared to specific design system examples being the "cherry on the cake." -

    -
    -

    Orthogonality Considerations

    -

    In scenarios where modifiers are not orthogonal, the resolver may need to enforce acceptable combinations and handle dependencies between modifiers. This can be achieved by:

    -
      -
    • Defining valid combinations explicitly.
    • -
    • Using nested modifiers or modifier groups.
    • -
    • Providing validation logic to prevent invalid input combinations.
    • -
    -
    -
    -
    -

    Example 1: Brand Theming

    - -

    Scenario: A company has multiple brands—Brand A and Brand B. Each brand has its own color palette and typography. Components like buttons and headers need to adapt based on the selected brand.

    -

    - These examples could be moved to the Resolver Spec netlify app for better presentation. If it uses Astro, interactive code tabs could be added to make the examples more engaging and easier to understand through hands-on exploration. -

    -
    -

    Resolver Definition

    -
    -{
    -  "name": "Brand Theming Resolver",
    -  "sets": [
    -    {
    -      "name": "base",
    -      "values": ["tokens/base.json"]
    -    },
    -    {
    -      "values": ["tokens/components.json"]
    -    }
    -  ],
    -  "modifiers": [
    -    {
    -      "name": "brand",
    -      "type": "enumerated",
    -      "values": [
    -        {
    -          "name": "brandA",
    -          "values": ["tokens/brands/brandA.json"]
    -        },
    -        {
    -          "name": "brandB",
    -          "values": ["tokens/brands/brandB.json"]
    -        }
    -      ],
    -      "meta": {
    -        "default": "brandA",
    -        "alias": "brand"
    -      }
    -    }
    -  ]
    -}
    -        
    -
    -
    -

    Token Files

    -

    tokens/base.json

    -
    -{
    -  "color": {
    -    "text": {
    -      "primary": {
    -        "value": "#000000",
    -        "type": "color"
    -      }
    -    }
    -  },
    -  "font": {
    -    "family": {
    -      "default": {
    -        "value": "Arial, sans-serif",
    -        "type": "font"
    -      }
    -    }
    -  }
    -}
    -        
    -

    tokens/components.json

    -
    -{
    -  "button": {
    -    "background": {
    -      "value": "{brand.color.primary}",
    -      "type": "color"
    -    },
    -    "fontFamily": {
    -      "value": "{brand.font.family}",
    -      "type": "font"
    -    }
    -  },
    -  "header": {
    -    "color": {
    -      "value": "{brand.color.secondary}",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    tokens/brands/brandA.json

    -
    -{
    -  "color": {
    -    "primary": {
    -      "value": "#FF5733",
    -      "type": "color"
    -    },
    -    "secondary": {
    -      "value": "#C70039",
    -      "type": "color"
    -    }
    -  },
    -  "font": {
    -    "family": {
    -      "value": "'Helvetica Neue', sans-serif",
    -      "type": "font"
    -    }
    -  }
    -}
    -        
    -

    tokens/brands/brandB.json

    -
    -{
    -  "color": {
    -    "primary": {
    -      "value": "#1F618D",
    -      "type": "color"
    -    },
    -    "secondary": {
    -      "value": "#2874A6",
    -      "type": "color"
    -    }
    -  },
    -  "font": {
    -    "family": {
    -      "value": "'Times New Roman', serif",
    -      "type": "font"
    -    }
    -  }
    -}
    -        
    -
    -
    -

    Input

    -
    -{
    -  "brand": "brandB"
    -}
    -        
    -
    -
    -

    Resolution Steps

    -

    Input Validation

    -
      -
    • Confirm that "brand" is a defined modifier.
    • -
    • Verify that "brandB" is a valid value for the "brand" modifier.
    • -
    -

    Base Set Flattening

    -

    Load tokens/base.json and tokens/components.json.

    -

    Merge tokens:

    -
    -{
    -  "color": {
    -    "text": {
    -      "primary": {
    -        "value": "#000000",
    -        "type": "color"
    -      }
    -    }
    -  },
    -  "font": {
    -    "family": {
    -      "default": {
    -        "value": "Arial, sans-serif",
    -        "type": "font"
    -      }
    -    }
    -  },
    -  "button": {
    -    "background": {
    -      "value": "{brand.color.primary}",
    -      "type": "color"
    -    },
    -    "fontFamily": {
    -      "value": "{brand.font.family}",
    -      "type": "font"
    -    }
    -  },
    -  "header": {
    -    "color": {
    -      "value": "{brand.color.secondary}",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    Modifier Application

    -

    Apply the "brand" modifier with value "brandB".

    -

    Load tokens/brands/brandB.json.

    -

    Apply aliasing as per meta.alias ("brand"), resulting in:

    -
    -{
    -  "brand": {
    -    "color": {
    -      "primary": {
    -        "value": "#1F618D",
    -        "type": "color"
    -      },
    -      "secondary": {
    -        "value": "#2874A6",
    -        "type": "color"
    -      }
    -    },
    -    "font": {
    -      "family": {
    -        "value": "'Times New Roman', serif",
    -        "type": "font"
    -      }
    -    }
    -  }
    -}
    -        
    -

    Alias Resolution

    -
      -
    • Resolve {brand.color.primary} in button.background: -
        -
      • Replace with #1F618D.
      • -
      -
    • -
    • Resolve {brand.font.family} in button.fontFamily: -
        -
      • Replace with 'Times New Roman', serif.
      • -
      -
    • -
    • Resolve {brand.color.secondary} in header.color: -
        -
      • Replace with #2874A6.
      • -
      -
    • -
    -

    Conflict Resolution

    -

    No conflicts in this example.

    -

    Final Output

    -
    -{
    -  "color": {
    -    "text": {
    -      "primary": {
    -        "value": "#000000",
    -        "type": "color"
    -      }
    -    }
    -  },
    -  "font": {
    -    "family": {
    -      "default": {
    -        "value": "Arial, sans-serif",
    -        "type": "font"
    -      }
    -    }
    -  },
    -  "button": {
    -    "background": {
    -      "value": "#1F618D",
    -      "type": "color"
    -    },
    -    "fontFamily": {
    -      "value": "'Times New Roman', serif",
    -      "type": "font"
    -    }
    -  },
    -  "header": {
    -    "color": {
    -      "value": "#2874A6",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    Explanation:

    -
      -
    • By changing the "brand" modifier, we can switch between different brand themes without altering the base token definitions.
    • -
    • The resolver efficiently manages the brand-specific overrides and applies them to components.
    • -
    -
    -
    - -
    -

    Example 2: Component States

    -

    Scenario: A design system includes buttons that change appearance based on their state—default, hover, active, and disabled. We want to manage these state-specific styles using modifiers.

    -
    -

    Resolver Definition

    -
    -{
    -  "name": "Component States Resolver",
    -  "sets": [
    -    {
    -      "values": ["tokens/components/button.json"]
    -    }
    -  ],
    -  "modifiers": [
    -    {
    -      "name": "state",
    -      "type": "enumerated",
    -      "values": [
    -        {
    -          "name": "default",
    -          "values": ["tokens/states/default.json"]
    -        },
    -        {
    -          "name": "hover",
    -          "values": ["tokens/states/hover.json"]
    -        },
    -        {
    -          "name": "active",
    -          "values": ["tokens/states/active.json"]
    -        },
    -        {
    -          "name": "disabled",
    -          "values": ["tokens/states/disabled.json"]
    -        }
    -      ],
    -      "meta": {
    -        "default": "default"
    -      }
    -    }
    -  ]
    -}
    -        
    -
    -
    -

    Token Files

    -

    tokens/components/button.json

    -
    -{
    -  "button": {
    -    "background": {
    -      "value": "{state.background}",
    -      "type": "color"
    -    },
    -    "textColor": {
    -      "value": "{state.textColor}",
    -      "type": "color"
    -    },
    -    "borderColor": {
    -      "value": "{state.borderColor}",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    tokens/states/default.json

    -
    -{
    -  "background": {
    -    "value": "#FFFFFF",
    -    "type": "color"
    -  },
    -  "textColor": {
    -    "value": "#000000",
    -    "type": "color"
    -  },
    -  "borderColor": {
    -    "value": "#CCCCCC",
    -    "type": "color"
    -  }
    -}
    -        
    -

    tokens/states/hover.json

    -
    -{
    -  "background": {
    -    "value": "#F0F0F0",
    -    "type": "color"
    -  },
    -  "textColor": {
    -    "value": "#000000",
    -    "type": "color"
    -  },
    -  "borderColor": {
    -    "value": "#BBBBBB",
    -    "type": "color"
    -  }
    -}
    -        
    -

    tokens/states/active.json

    -
    -{
    -  "background": {
    -    "value": "#E0E0E0",
    -    "type": "color"
    -  },
    -  "textColor": {
    -    "value": "#000000",
    -    "type": "color"
    -  },
    -  "borderColor": {
    -    "value": "#AAAAAA",
    -    "type": "color"
    -  }
    -}
    -        
    -

    tokens/states/disabled.json

    -
    -{
    -  "background": {
    -    "value": "#F9F9F9",
    -    "type": "color"
    -  },
    -  "textColor": {
    -    "value": "#777777",
    -    "type": "color"
    -  },
    -  "borderColor": {
    -    "value": "#DDDDDD",
    -    "type": "color"
    -  }
    -}
    -        
    -
    -
    -

    Input

    -
    -{
    -  "state": "hover"
    -}
    -        
    -
    -
    -

    Resolution Steps

    -

    Input Validation

    -
      -
    • Confirm that "state" is a defined modifier.
    • -
    • Verify that "hover" is a valid value for the "state" modifier.
    • -
    -

    Base Set Flattening

    -

    Load tokens/components/button.json.

    -

    Tokens:

    -
    -{
    -  "button": {
    -    "background": {
    -      "value": "{state.background}",
    -      "type": "color"
    -    },
    -    "textColor": {
    -      "value": "{state.textColor}",
    -      "type": "color"
    -    },
    -    "borderColor": {
    -      "value": "{state.borderColor}",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    Modifier Application

    -

    Apply the "state" modifier with value "hover".

    -

    Load tokens/states/hover.json.

    -

    Tokens under the "state" namespace:

    -
    -{
    -  "state": {
    -    "background": {
    -      "value": "#F0F0F0",
    -      "type": "color"
    -    },
    -    "textColor": {
    -      "value": "#000000",
    -      "type": "color"
    -    },
    -    "borderColor": {
    -      "value": "#BBBBBB",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    Alias Resolution

    -
      -
    • Resolve {state.background} in button.background: -
        -
      • Replace with #F0F0F0.
      • -
      -
    • -
    • Resolve {state.textColor} in button.textColor: -
        -
      • Replace with #000000.
      • -
      -
    • -
    • Resolve {state.borderColor} in button.borderColor: -
        -
      • Replace with #BBBBBB.
      • -
      -
    • -
    -

    Conflict Resolution

    -

    No conflicts in this example.

    -

    Final Output

    -
    -{
    -  "button": {
    -    "background": {
    -      "value": "#F0F0F0",
    -      "type": "color"
    -    },
    -    "textColor": {
    -      "value": "#000000",
    -      "type": "color"
    -    },
    -    "borderColor": {
    -      "value": "#BBBBBB",
    -      "type": "color"
    -    }
    -  }
    -}
    -        
    -

    Explanation:

    -
      -
    • By changing the "state" modifier, we can easily generate the tokens for different button states.
    • -
    • This approach keeps state-specific styles organized and separate from component definitions.
    • -
    -
    -
    - -
    -

    Example 3: Responsive Design

    -

    Scenario: A design system needs to support responsive design by adjusting spacing and typography based on screen sizes—mobile, tablet, and desktop. We want to manage these variations using modifiers.

    -
    -

    Resolver Definition

    -
    -{
    -  "name": "Responsive Design Resolver",
    -  "sets": [
    -    {
    -      "name": "core",
    -      "values": ["tokens/core.json"]
    -    }
    -  ],
    -  "modifiers": [
    -    {
    -      "name": "screenSize",
    -      "type": "enumerated",
    -      "values": [
    -        {
    -          "name": "mobile",
    -          "values": ["tokens/screens/mobile.json"]
    -        },
    -        {
    -          "name": "tablet",
    -          "values": ["tokens/screens/tablet.json"]
    -        },
    -        {
    -          "name": "desktop",
    -          "values": ["tokens/screens/desktop.json"]
    -        }
    -      ],
    -      "meta": {
    -        "default": "mobile",
    -        "alias": "screen"
    -      }
    -    }
    -  ]
    -}
    -        
    -
    -
    -

    Token Files

    -

    tokens/core.json

    -
    -{
    -  "spacing": {
    -    "small": {
    -      "value": "{screen.spacing.small}",
    -      "type": "dimension"
    -    },
    -    "medium": {
    -      "value": "{screen.spacing.medium}",
    -      "type": "dimension"
    -    },
    -    "large": {
    -      "value": "{screen.spacing.large}",
    -      "type": "dimension"
    -    }
    -  },
    -  "typography": {
    -    "fontSize": {
    -      "value": "{screen.typography.fontSize}",
    -      "type": "dimension"
    -    }
    -  }
    -}
    -        
    -

    tokens/screens/mobile.json

    -
    -{
    -  "spacing": {
    -    "small": {
    -      "value": "4px",
    -      "type": "dimension"
    -    },
    -    "medium": {
    -      "value": "8px",
    -      "type": "dimension"
    -    },
    -    "large": {
    -      "value": "12px",
    -      "type": "dimension"
    -    }
    -  },
    -  "typography": {
    -    "fontSize": {
    -      "value": "14px",
    -      "type": "dimension"
    -    }
    -  }
    -}
    -        
    -

    tokens/screens/tablet.json

    -
    -{
    -  "spacing": {
    -    "small": {
    -      "value": "6px",
    -      "type": "dimension"
    -    },
    -    "medium": {
    -      "value": "12px",
    -      "type": "dimension"
    -    },
    -    "large": {
    -      "value": "18px",
    -      "type": "dimension"
    -    }
    -  },
    -  "typography": {
    -    "fontSize": {
    -      "value": "16px",
    -      "type": "dimension"
    -    }
    -  }
    -}
    -        
    -

    tokens/screens/desktop.json

    -
    -{
    -  "spacing": {
    -    "small": {
    -      "value": "8px",
    -      "type": "dimension"
    -    },
    -    "medium": {
    -      "value": "16px",
    -      "type": "dimension"
    -    },
    -    "large": {
    -      "value": "24px",
    -      "type": "dimension"
    -    }
    -  },
    -  "typography": {
    -    "fontSize": {
    -      "value": "18px",
    -      "type": "dimension"
    -    }
    -  }
    -}
    -        
    -
    -
    -

    Input

    -
    -{
    -  "screenSize": "desktop"
    -}
    -        
    -
    -
    -

    Resolution Steps

    -

    Input Validation

    -
      -
    • Confirm that "screenSize" is a defined modifier.
    • -
    • Verify that "desktop" is a valid value for the "screenSize" modifier.
    • -
    -

    Base Set Flattening

    -

    Load tokens/core.json.

    -

    Tokens:

    -
    -{
    -  "spacing": {
    -    "small": {
    -      "value": "{screen.spacing.small}",
    -      "type": "dimension"
    -    },
    -    "medium": {
    -      "value": "{screen.spacing.medium}",
    -      "type": "dimension"
    -    },
    -    "large": {
    -      "value": "{screen.spacing.large}",
    -      "type": "dimension"
    -    }
    -  },
    -  "typography": {
    -    "fontSize": {
    -      "value": "{screen.typography.fontSize}",
    -      "type": "dimension"
    -    }
    -  }
    -}
    -        
    -

    Modifier Application

    -

    Apply the "screenSize" modifier with value "desktop".

    -

    Load tokens/screens/desktop.json.

    -

    Apply aliasing as per meta.alias ("screen"), resulting in:

    -
    -{
    -  "screen": {
    -    "spacing": {
    -      "small": {
    -        "value": "8px",
    -        "type": "dimension"
    -      },
    -      "medium": {
    -        "value": "16px",
    -        "type": "dimension"
    -      },
    -      "large": {
    -        "value": "24px",
    -        "type": "dimension"
    -      }
    -    },
    -    "typography": {
    -      "fontSize": {
    -        "value": "18px",
    -        "type": "dimension"
    -      }
    -    }
    -  }
    -}
    -        
    -

    Alias Resolution

    -
      -
    • Resolve {screen.spacing.small} in spacing.small: -
        -
      • Replace with 8px.
      • -
      -
    • -
    • Resolve {screen.spacing.medium} in spacing.medium: -
        -
      • Replace with 16px.
      • -
      -
    • -
    • Resolve {screen.spacing.large} in spacing.large: -
        -
      • Replace with 24px.
      • -
      -
    • -
    • Resolve {screen.typography.fontSize} in typography.fontSize: -
        -
      • Replace with 18px.
      • -
      -
    • -
    -

    Conflict Resolution

    -

    No conflicts in this example.

    -

    Final Output

    -
    -{
    -  "spacing": {
    -    "small": {
    -      "value": "8px",
    -      "type": "dimension"
    -    },
    -    "medium": {
    -      "value": "16px",
    -      "type": "dimension"
    -    },
    -    "large": {
    -      "value": "24px",
    -      "type": "dimension"
    -    }
    -  },
    -  "typography": {
    -    "fontSize": {
    -      "value": "18px",
    -      "type": "dimension"
    -    }
    -  }
    -}
    -        
    -

    Explanation:

    -
      -
    • By changing the "screenSize" modifier, we can generate tokens appropriate for different devices.
    • -
    • This method centralizes responsive adjustments, making it easier to maintain and update.
    • -
    -
    -
    - +
    + +
    + +
    + +
    - \ No newline at end of file + diff --git a/technical-reports/resolver/introduction.md b/technical-reports/resolver/introduction.md new file mode 100644 index 0000000..3d033aa --- /dev/null +++ b/technical-reports/resolver/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Design tokens are key-value pairs that represent design decisions, allowing for consistent use across different platforms and tools. In complex design systems, there is a need to manage multiple dimensions such as themes, modes (e.g., light and dark), brands, and variants. This introduces challenges in organizing tokens, resolving references, and applying overrides. + +The Resolver Specification addresses these challenges by introducing a structured mechanism to: + +- Organize tokens into **sets** and **modifiers**. +- Define how tokens are **resolved** when multiple sets and modifiers are involved. +- Support **scalability** by handling arbitrary dimensions without complicating the core token definitions. +- Maintain **modularity** and **reusability** by allowing tokens to be combined and overridden in a controlled manner. diff --git a/technical-reports/resolver/orthogonality.md b/technical-reports/resolver/orthogonality.md new file mode 100644 index 0000000..7aa5542 --- /dev/null +++ b/technical-reports/resolver/orthogonality.md @@ -0,0 +1,26 @@ +# Orthogonality + +Modifiers are considered **orthogonal** when they can be changed independently without affecting each other's resolution logic. + + + + diff --git a/technical-reports/resolver/rationale.md b/technical-reports/resolver/rationale.md new file mode 100644 index 0000000..b3a5b7e --- /dev/null +++ b/technical-reports/resolver/rationale.md @@ -0,0 +1,48 @@ +## Rationale + +### Context and Problem Statement + +In design systems, especially those at scale, managing multiple themes, modes, brands, and other modifiers can become complex. Designers and developers often struggle with: + +- **Naming Conflicts**: Using complex naming conventions to differentiate tokens across different themes or modes, leading to brittle and hard-to-maintain token names. +- **Scalability Issues**: Difficulty in scaling token management when new dimensions are added, such as additional themes or brands. +- **Tooling Limitations**: Existing tools may not support multi-dimensional token resolution, forcing teams to create workarounds that complicate their workflows. +- **Separation of Concerns**: Embedding resolution logic within token files mixes data with behavior, violating separation of + concerns. + +### Parameters + +- **Simplicity**: The solution should keep token files simple and editable by hand, without requiring complex tooling. +- **Scalability**: It should support arbitrary dimensions (e.g., themes, modes, brands) without significant overhead. +- **Modularity**: Tokens should be organized in a way that allows for modularity and reusability. +- **Separation of Concerns**: Resolution logic should be externalized from token definitions. + +### Background + +Existing approaches, such as embedding modifiers directly into token names or using global theme files, have limitations. They often lead to: + +- **Global Namespace Pollution**: All tokens exist in a single global namespace, increasing the likelihood of naming conflicts. +- **High Cognitive Load**: Designers and developers must remember complex naming conventions and how different tokens relate across dimensions. +- **Performance Issues**: Resolving tokens in systems with many dimensions can be slow due to the combinatorial explosion of permutations. + +### Motivation + +The Resolver Specification aims to: + +- **Simplify Token Management**: By externalizing resolution logic, token files remain simple and focused on defining values. +- **Enhance Modularity**: Tokens can be organized into sets and modifiers, allowing teams to work on isolated components or themes without affecting others. +- **Improve Performance**: By reducing the scope of tokens in resolution, the process becomes faster and more efficient. + + + +- **Facilitate Tool Integration**: Providing a standard way to define resolution logic makes it easier for tools to integrate and automate token management. + + diff --git a/technical-reports/resolver/resolution-aliasing.md b/technical-reports/resolver/resolution-aliasing.md new file mode 100644 index 0000000..76bd2f1 --- /dev/null +++ b/technical-reports/resolver/resolution-aliasing.md @@ -0,0 +1,155 @@ +# Resolution Aliasing + +Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts. + + + +**Example:** + + + + diff --git a/technical-reports/resolver/resolution-example.md b/technical-reports/resolver/resolution-example.md new file mode 100644 index 0000000..0f42c35 --- /dev/null +++ b/technical-reports/resolver/resolution-example.md @@ -0,0 +1,199 @@ +# Resolver Resolution Example + + diff --git a/technical-reports/resolver/resolution-logic.md b/technical-reports/resolver/resolution-logic.md new file mode 100644 index 0000000..3367a74 --- /dev/null +++ b/technical-reports/resolver/resolution-logic.md @@ -0,0 +1,94 @@ +# Resolution Logic + +The resolution process involves the following steps: + +1. **Input Validation**: Ensure the provided inputs match the expected modifiers and their acceptable values. + + + +2. **Base Set Flattening**: Load and merge the token sets specified in the **sets** array. Later sets override earlier ones if there are naming conflicts. + + + + + +3. **Modifier Application**: Apply the selected modifiers based on the inputs. Modifiers can override tokens from the base sets or introduce new tokens. + + + +4. **Aliasing and Namespacing**: Apply any aliasing specified in the modifiers to namespace or rename tokens. + + + +5. **Alias Resolution**: Resolve any token references (aliases) using the combined tokens from the base sets and modifiers. + +6. **Conflict Resolution**: In case of conflicting tokens (same name and path), later modifiers or sets override earlier ones. + +7. **Circular Reference Detection**: Detect and handle circular references as errors. + +8. **Final Output**: Produce a flat, resolved set of tokens ready for consumption. + +### Detailed Steps + +#### Input Validation + +- Verify that all provided modifier inputs correspond to defined + modifiers. +- Check that the input values are among the acceptable options + defined in the modifiers. + +#### Base Set Flattening + +- Load each token set in the order specified. +- Merge the tokens, with later sets overriding earlier ones on name conflicts. + +#### Modifier Application + +- For each modifier: + - Apply any aliasing or namespacing specified in meta. + - Load the token sets associated with the selected modifier value. + - Merge these tokens with the base tokens, applying overrides as necessary. + +#### Alias Resolution + + + +- Iterate over all tokens to find references (e.g., {theme.accent}). +- Resolve references first within the same token set. +- If not found, resolve references from the modifiers, following the order of precedence. +- Handle nested references recursively. + +#### Conflict Resolution + +- In case of conflicting tokens: + - Tokens from modifiers override tokens from base sets. + - If multiple modifiers define the same token, the last applied modifier takes precedence. + +#### Circular Reference Detection + +- Detect any circular references during alias resolution. +- If a circular reference is found, throw an error and halt the resolution process. diff --git a/technical-reports/resolver/terminology.md b/technical-reports/resolver/terminology.md new file mode 100644 index 0000000..f657b1f --- /dev/null +++ b/technical-reports/resolver/terminology.md @@ -0,0 +1,103 @@ +# Resolver Terminology + +
    +
    Resolver
    +
    + +A mechanism that combines token sets and applies modifiers to produce a final, resolved set of design tokens. + +
    +
    Token Set
    +
    + +A collection of design tokens grouped together, often representing foundational tokens, component-specific tokens, or theme-specific tokens. + +
    +
    Dimension
    +
    + +Dimensions are categories used to organize your design tokens. See them as contexts in which token values might change: + +- Brands +- Surface +- Language direction +- Themes +- Platform +- Screen size +- Density +- Component +- State +- Variant +- Contrast + + + +
    + +
    Modifier
    + +
    + +An entity that modifies or overrides tokens in the base sets. Modifiers can represent dimensions like themes, modes, brands, or any other contextual variations. + +
    + +
    Enumerated Modifier
    +
    + +A modifier with predefined, named values (e.g., "light", "dark" for a theme modifier). + +
    +
    Include Modifier
    +
    + +A modifier that replaces or includes entire token sets during resolution. + +
    +
    Alias
    +
    + +An optional property that allows for namespacing or renaming token paths during resolution. + + + +
    + +
    + +
    + +
    Input
    +
    + +Parameters provided to the resolver to specify which modifiers to apply during the resolution process. + +
    +
    Resolution
    +
    + +The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens. + + + +
    + +
    Orthogonality
    +
    + +The property of modifiers being independent of each other, allowing them to be combined freely without affecting each other's resolution logic. + +
    +
    diff --git a/technical-reports/resolver/use-cases.md b/technical-reports/resolver/use-cases.md new file mode 100644 index 0000000..b061cf4 --- /dev/null +++ b/technical-reports/resolver/use-cases.md @@ -0,0 +1,25 @@ +# Use Cases + +### Style Dictionary Integration + +The Resolver Specification can be integrated with tools like [Style Dictionary](https://amzn.github.io/style-dictionary/) to automate the generation of platform-specific design tokens. + + + + From e4aeb6869260db949d9a3e0fac1e0c373a496149 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Tue, 12 Aug 2025 09:13:18 -0600 Subject: [PATCH 4/9] Improve language of introduction (#291) --- technical-reports/resolver/introduction.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/technical-reports/resolver/introduction.md b/technical-reports/resolver/introduction.md index 3d033aa..1d831c1 100644 --- a/technical-reports/resolver/introduction.md +++ b/technical-reports/resolver/introduction.md @@ -1,10 +1,3 @@ # Introduction -Design tokens are key-value pairs that represent design decisions, allowing for consistent use across different platforms and tools. In complex design systems, there is a need to manage multiple dimensions such as themes, modes (e.g., light and dark), brands, and variants. This introduces challenges in organizing tokens, resolving references, and applying overrides. - -The Resolver Specification addresses these challenges by introducing a structured mechanism to: - -- Organize tokens into **sets** and **modifiers**. -- Define how tokens are **resolved** when multiple sets and modifiers are involved. -- Support **scalability** by handling arbitrary dimensions without complicating the core token definitions. -- Maintain **modularity** and **reusability** by allowing tokens to be combined and overridden in a controlled manner. +This module describes the means by which design tokens may express multiple values for the purposes of “theming” (light mode, dark mode, high contrast mode, etc.) or other scenarios. From 767c0ad26ab38846020b1761d8137c0ed75c1ace Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Tue, 12 Aug 2025 09:13:59 -0600 Subject: [PATCH 5/9] 2. Terminology and 3. Syntax (#292) --- technical-reports/resolver/index.html | 6 +- technical-reports/resolver/rationale.md | 48 ------------ technical-reports/resolver/syntax.md | 37 +++++++++ technical-reports/resolver/terminology.md | 96 ++++------------------- 4 files changed, 54 insertions(+), 133 deletions(-) delete mode 100644 technical-reports/resolver/rationale.md create mode 100644 technical-reports/resolver/syntax.md diff --git a/technical-reports/resolver/index.html b/technical-reports/resolver/index.html index 91634af..48c20dc 100644 --- a/technical-reports/resolver/index.html +++ b/technical-reports/resolver/index.html @@ -85,14 +85,12 @@ >
    diff --git a/technical-reports/resolver/rationale.md b/technical-reports/resolver/rationale.md deleted file mode 100644 index b3a5b7e..0000000 --- a/technical-reports/resolver/rationale.md +++ /dev/null @@ -1,48 +0,0 @@ -## Rationale - -### Context and Problem Statement - -In design systems, especially those at scale, managing multiple themes, modes, brands, and other modifiers can become complex. Designers and developers often struggle with: - -- **Naming Conflicts**: Using complex naming conventions to differentiate tokens across different themes or modes, leading to brittle and hard-to-maintain token names. -- **Scalability Issues**: Difficulty in scaling token management when new dimensions are added, such as additional themes or brands. -- **Tooling Limitations**: Existing tools may not support multi-dimensional token resolution, forcing teams to create workarounds that complicate their workflows. -- **Separation of Concerns**: Embedding resolution logic within token files mixes data with behavior, violating separation of - concerns. - -### Parameters - -- **Simplicity**: The solution should keep token files simple and editable by hand, without requiring complex tooling. -- **Scalability**: It should support arbitrary dimensions (e.g., themes, modes, brands) without significant overhead. -- **Modularity**: Tokens should be organized in a way that allows for modularity and reusability. -- **Separation of Concerns**: Resolution logic should be externalized from token definitions. - -### Background - -Existing approaches, such as embedding modifiers directly into token names or using global theme files, have limitations. They often lead to: - -- **Global Namespace Pollution**: All tokens exist in a single global namespace, increasing the likelihood of naming conflicts. -- **High Cognitive Load**: Designers and developers must remember complex naming conventions and how different tokens relate across dimensions. -- **Performance Issues**: Resolving tokens in systems with many dimensions can be slow due to the combinatorial explosion of permutations. - -### Motivation - -The Resolver Specification aims to: - -- **Simplify Token Management**: By externalizing resolution logic, token files remain simple and focused on defining values. -- **Enhance Modularity**: Tokens can be organized into sets and modifiers, allowing teams to work on isolated components or themes without affecting others. -- **Improve Performance**: By reducing the scope of tokens in resolution, the process becomes faster and more efficient. - - - -- **Facilitate Tool Integration**: Providing a standard way to define resolution logic makes it easier for tools to integrate and automate token management. - - diff --git a/technical-reports/resolver/syntax.md b/technical-reports/resolver/syntax.md new file mode 100644 index 0000000..be36ca1 --- /dev/null +++ b/technical-reports/resolver/syntax.md @@ -0,0 +1,37 @@ +# Syntax + +## <token-defs> + +An array consisting of: + +- `string` which MUST be a valid URI pointing to a valid Tokens JSON file, or +- Inline objects that MUST follow the JSON format. + +Any other inputs are invalid. + +The array order is significant, where the files are merged in order. In case of a conflict, the last value in the array will overwrite any previous values. + + diff --git a/technical-reports/resolver/terminology.md b/technical-reports/resolver/terminology.md index f657b1f..5039e7d 100644 --- a/technical-reports/resolver/terminology.md +++ b/technical-reports/resolver/terminology.md @@ -1,103 +1,37 @@ -# Resolver Terminology +# Terminology -
    -
    Resolver
    -
    +## Resolver -A mechanism that combines token sets and applies modifiers to produce a final, resolved set of design tokens. +The mechanism by which multiple possible values of design tokens are reduced to a single value, i.e. this module. -
    -
    Token Set
    -
    +## Set -A collection of design tokens grouped together, often representing foundational tokens, component-specific tokens, or theme-specific tokens. +Alternate design token values that are defined in [<token-defs>](#token-defs), intended to be merged in a specific order. -
    -
    Dimension
    -
    +## Modifier -Dimensions are categories used to organize your design tokens. See them as contexts in which token values might change: +Alternate design token value accessed with an [=input=], that takes priority over a [=set=]. Modifiers MAY be [=orthogonal=]. -- Brands -- Surface -- Language direction -- Themes -- Platform -- Screen size -- Density -- Component -- State -- Variant -- Contrast +## Input - - -
    - -
    Modifier
    - -
    - -An entity that modifies or overrides tokens in the base sets. Modifiers can represent dimensions like themes, modes, brands, or any other contextual variations. - -
    - -
    Enumerated Modifier
    -
    - -A modifier with predefined, named values (e.g., "light", "dark" for a theme modifier). - -
    -
    Include Modifier
    -
    - -A modifier that replaces or includes entire token sets during resolution. +Parameters provided to the [=resolver=] to specify which [=modifier=]s to apply during the [resolution](#resolution) process. -
    -
    Alias
    -
    +## Resolution -An optional property that allows for namespacing or renaming token paths during resolution. +The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens. -
    - -
    - -
    - -
    Input
    -
    - -Parameters provided to the resolver to specify which modifiers to apply during the resolution process. - -
    -
    Resolution
    -
    - -The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens. - -
    - -
    Orthogonality
    -
    - -The property of modifiers being independent of each other, allowing them to be combined freely without affecting each other's resolution logic. +## Orthogonal (Orthogonality) -
    -
    +The property of [=modifier=]s being independent of each other, allowing them to be combined freely without affecting each other's [=resolution=] logic. From a51e04980862d2ef1b57586ad5cf090f3fb3e915 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Sun, 17 Aug 2025 18:12:18 -0600 Subject: [PATCH 6/9] Remaining edits to the resolver specification (#297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: edit introduction, add orthogonality to terminology * chore: update Netlify (#295) * docs: edit Syntax and Resolver Logic * fix(docs): don’t run htmlmin on ReSpec (#296) * Final edits --- package.json | 4 +- pnpm-lock.yaml | 10 +- technical-reports/package.json | 14 +- .../additional-context-and-information.md | 78 --- technical-reports/resolver/conformance.md | 6 +- technical-reports/resolver/example1.md | 290 ----------- technical-reports/resolver/example2.md | 253 ---------- technical-reports/resolver/example3.md | 283 ----------- technical-reports/resolver/file-format.md | 127 ----- .../resolver/future-extensions.md | 7 - technical-reports/resolver/index.html | 79 +-- technical-reports/resolver/introduction.md | 104 +++- technical-reports/resolver/orthogonality.md | 26 - .../resolver/resolution-aliasing.md | 155 ------ .../resolver/resolution-example.md | 199 -------- .../resolver/resolution-logic.md | 460 +++++++++++++++--- technical-reports/resolver/syntax.md | 444 ++++++++++++++++- technical-reports/resolver/terminology.md | 20 +- technical-reports/resolver/use-cases.md | 25 - www/.eleventy.js | 26 +- 20 files changed, 990 insertions(+), 1620 deletions(-) delete mode 100644 technical-reports/resolver/additional-context-and-information.md delete mode 100644 technical-reports/resolver/example1.md delete mode 100644 technical-reports/resolver/example2.md delete mode 100644 technical-reports/resolver/example3.md delete mode 100644 technical-reports/resolver/file-format.md delete mode 100644 technical-reports/resolver/future-extensions.md delete mode 100644 technical-reports/resolver/orthogonality.md delete mode 100644 technical-reports/resolver/resolution-aliasing.md delete mode 100644 technical-reports/resolver/resolution-example.md delete mode 100644 technical-reports/resolver/use-cases.md diff --git a/package.json b/package.json index adc6478..7d22bb0 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,12 @@ "license": "SEE LICENSE.md", "packageManager": "pnpm@10.12.1", "scripts": { - "build": "pnpm --filter @dtcg/www run build", + "build": "pnpm --filter @dtcg/tr run build && pnpm --filter @dtcg/www run build", "dev": "pnpm run build && pnpm --parallel --recursive --if-present run dev", "lint": "pnpm --recursive --parallel --stream --if-present run lint", "format": "pnpm --recursive --parallel --stream --if-present run format", "prepare": "husky", - "install-browsers": "puppeteer browsers install chrome@137" + "install-browsers": "puppeteer browsers install chrome" }, "lint-staged": { "*.{md,yml,json,html}": "prettier --write" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bc630e..cfbc297 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 respec: - specifier: ^35.5.0 - version: 35.5.0 + specifier: ^35.5.1 + version: 35.5.1 www: devDependencies: @@ -1650,8 +1650,8 @@ packages: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} deprecated: https://github.com/lydell/resolve-url#deprecated - respec@35.5.0: - resolution: {integrity: sha512-POjDng/tc07oP3dEreR1nGKGBBJlKnaa5ycAa4kwCV1JI0ZPlNIbUz1F/Ce9XuA7/L0Pl381OyTASmndfjDrXQ==} + respec@35.5.1: + resolution: {integrity: sha512-F1ykHL5WdMXb6Rp5EGjaPsc34SSIl8GWFRKlkFZ+xNhsQrU5cN9OsnxWYqP/XO/0Kkeyj7JKn4pC1Ykrqnj7LQ==} engines: {node: '>=20.12.1'} hasBin: true @@ -3757,7 +3757,7 @@ snapshots: resolve-url@0.2.1: {} - respec@35.5.0: + respec@35.5.1: dependencies: colors: 1.4.0 finalhandler: 2.1.0 diff --git a/technical-reports/package.json b/technical-reports/package.json index fdeb4cf..9f236be 100644 --- a/technical-reports/package.json +++ b/technical-reports/package.json @@ -9,12 +9,12 @@ "directory": "technical-reports" }, "scripts": { - "build": "pnpm run build:index && pnpm run build:format && pnpm run build:color && pnpm run build:resolver", - "build:index": "pnpm run mkdirs && respec index.html ../www/TR/drafts/index.html --port 3001 --localhost --disable-sandbox", - "build:format": "pnpm run mkdirs && respec format/index.html ../www/TR/drafts/format/index.html --port 3002 --localhost --disable-sandbox", - "build:color": "pnpm run mkdirs && respec color/index.html ../www/TR/drafts/color/index.html --port 3003 --localhost --disable-sandbox", - "build:resolver": "pnpm run mkdirs && respec resolver/index.html ../www/TR/drafts/resolver/index.html --port 3004 --localhost --disable-sandbox", - "mkdirs": "mkdir -p ../www/TR/drafts && mkdir -p ../www/TR/drafts/format && mkdir -p ../www/TR/drafts/color", + "build": "pnpm run mkdirs && pnpm run build:index && pnpm run build:format && pnpm run build:color && pnpm run build:resolver", + "build:index": "respec index.html ../www/TR/drafts/index.html --port 3001 --localhost --disable-sandbox", + "build:format": "respec format/index.html ../www/TR/drafts/format/index.html --port 3002 --localhost --disable-sandbox", + "build:color": "respec color/index.html ../www/TR/drafts/color/index.html --port 3003 --localhost --disable-sandbox", + "build:resolver": "respec resolver/index.html ../www/TR/drafts/resolver/index.html --port 3004 --localhost --disable-sandbox", + "mkdirs": "mkdir -p ../www/TR/drafts && mkdir -p ../www/TR/drafts/format && mkdir -p ../www/TR/drafts/color && mkdir -p ../www/TR/drafts/resolver", "dev": "pnpm --parallel --filter @dtcg/tr run \"/^dev:.*/\"", "dev:index": "pnpm run mkdirs && chokidar \"index.html\" -c \"pnpm run build:index\" -d 5000", "dev:format": "pnpm run mkdirs && chokidar \"format/**/*\" -c \"pnpm run build:format\" -d 5000", @@ -30,6 +30,6 @@ "devDependencies": { "chokidar": "^4.0.3", "chokidar-cli": "^3.0.0", - "respec": "^35.5.0" + "respec": "^35.5.1" } } diff --git a/technical-reports/resolver/additional-context-and-information.md b/technical-reports/resolver/additional-context-and-information.md deleted file mode 100644 index 94fb3c6..0000000 --- a/technical-reports/resolver/additional-context-and-information.md +++ /dev/null @@ -1,78 +0,0 @@ -# Additional Context and Information - -## Resolution Aliasing - -Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts. - -

    - - - - - - - -## Orthogonality Considerations - -In scenarios where modifiers are not orthogonal, the resolver may need to enforce acceptable combinations and handle dependencies between modifiers. This can be achieved by: - -- Defining valid combinations explicitly. -- Using nested modifiers or modifier groups. -- Providing validation logic to prevent invalid input combinations. diff --git a/technical-reports/resolver/conformance.md b/technical-reports/resolver/conformance.md index 71eb6be..2c482b5 100644 --- a/technical-reports/resolver/conformance.md +++ b/technical-reports/resolver/conformance.md @@ -1,7 +1,7 @@ Tools implementing the Resolver Specification MUST: -- **Support the Resolution Process**: Implement the resolution logic as defined, including input validation, base set flattening, modifier application, aliasing, and conflict resolution. +- **Support the Resolution Process**: Implement the [resolution logic](#resolution-logic) as defined, including input validation, base set flattening, modifier application, aliasing, and conflict resolution. - **Validate Inputs**: Ensure that provided modifier inputs match the defined modifiers and acceptable values. -- **Resolve Aliases Correctly**: Handle token references accurately, including recursive references and detection of circular dependencies. -- **Preserve Token Properties**: Maintain additional token properties (e.g., description, type) throughout the resolution process. +- **Resolve Aliases Correctly**: Handle [token references](../format/#aliases-references) accurately, including recursive references and detection of circular dependencies. +- **Preserve Token Properties**: Maintain additional token properties (e.g., [$extensions](#extensions)) throughout the resolution process. - **Handle Errors Gracefully**: Provide meaningful error messages for issues like invalid inputs or circular references. diff --git a/technical-reports/resolver/example1.md b/technical-reports/resolver/example1.md deleted file mode 100644 index 017020c..0000000 --- a/technical-reports/resolver/example1.md +++ /dev/null @@ -1,290 +0,0 @@ -# Example 1: Brand Theming - - diff --git a/technical-reports/resolver/example2.md b/technical-reports/resolver/example2.md deleted file mode 100644 index 4cccbc4..0000000 --- a/technical-reports/resolver/example2.md +++ /dev/null @@ -1,253 +0,0 @@ -# Example 2: Component States - - diff --git a/technical-reports/resolver/example3.md b/technical-reports/resolver/example3.md deleted file mode 100644 index fe03001..0000000 --- a/technical-reports/resolver/example3.md +++ /dev/null @@ -1,283 +0,0 @@ -# Example 3: Responsive Design - - diff --git a/technical-reports/resolver/file-format.md b/technical-reports/resolver/file-format.md deleted file mode 100644 index e636521..0000000 --- a/technical-reports/resolver/file-format.md +++ /dev/null @@ -1,127 +0,0 @@ -# File Format - -A resolver is defined as a JSON object with the following properties: - -- **name** (optional): A human-readable name for the resolver. -- **description** (optional): A description of the resolver's purpose. -- **sets** (required): An array of token sets to be used as the base for resolution. -- **modifiers** (optional): An array of modifiers that can alter or override tokens from the base sets. - -### Token Sets - -Each token set in the **sets** array is an object with the following properties: - -- **name** (optional): An identifier for the set. -- **values** (required): An array of references to token files or inline token definitions. A reference MUST be a string containing a path to a token file. An inline token definition MUST be a JSON object containing a valid design token structure. -- **meta** (optional): Additional metadata, such as proprietary extensions. - - - - - -### Modifiers - -Modifiers are defined in the **modifiers** array and can have different types, affecting how they influence the resolution -process. - -Each modifier is an object with the following properties: - -- **name** (required): The name of the modifier. -- **type** (optional, default: "enumerated"): The type of modifier. This can be "enumerated" or "include". -- **values** (required): An array of possible values for the modifier. -- **meta** (optional): Additional metadata, such as default values or aliases. - - - - - - - - - - diff --git a/technical-reports/resolver/future-extensions.md b/technical-reports/resolver/future-extensions.md deleted file mode 100644 index 8ef6d97..0000000 --- a/technical-reports/resolver/future-extensions.md +++ /dev/null @@ -1,7 +0,0 @@ -# Future Extensions - -The Resolver Specification can be extended to handle more complex scenarios: - -- **Conditional Modifiers**: Modifiers that are applied based on conditions or contexts not specified directly in the input. -- **Dynamic Resolution**: Support for runtime evaluation of tokens based on environmental factors (e.g., screen size, user preferences). -- **Dependency Graphs**: Explicitly defining dependencies between modifiers to handle non-orthogonal scenarios. diff --git a/technical-reports/resolver/index.html b/technical-reports/resolver/index.html index 48c20dc..221a0ef 100644 --- a/technical-reports/resolver/index.html +++ b/technical-reports/resolver/index.html @@ -48,12 +48,9 @@

    - This document describes the technical specification for a resolver - mechanism used to manage and resolve design tokens in complex scenarios - involving themes, modes, brands, and other modifiers. It extends the - Design Tokens Format Specification by introducing a structured way to - combine and override token sets to produce a final, resolved set of - tokens for consumption by design tools and platforms. + This specification extends the [format](../format/) and describes a + method to work with alternate values for [design tokens](../), such as + “light mode” and “dark mode” color themes for supporting devices.

    @@ -95,46 +92,10 @@ >
    - -
    - -
    -
    - -
    - -
    - -
    -

    Acknowledgments

    - We thank the members of the Design Tokens Community Group for their - contributions and feedback, including: + This resolver spec wouldn’t have happened without the Hyma Team, + including but not limited to Mike Kamminga, Andrew L'Homme, and Lilith. + Significant contributions were also made by Joren Broekema, Louis + Chenais. We thank the members of the Design Tokens Community Group for + their contributions and feedback.

    -
      -
    • [Contributors' Names]
    • -
    - -
    - -
    - -
    - -
    diff --git a/technical-reports/resolver/introduction.md b/technical-reports/resolver/introduction.md index 1d831c1..e913fcc 100644 --- a/technical-reports/resolver/introduction.md +++ b/technical-reports/resolver/introduction.md @@ -1,3 +1,105 @@ # Introduction -This module describes the means by which design tokens may express multiple values for the purposes of “theming” (light mode, dark mode, high contrast mode, etc.) or other scenarios. +Expressing alternate values for design tokens multiples the number of values to manage for every layer. This specification describes an efficient way to work with [=alternate values=]while producing the fewest minimal end number. We’ll compare a [naïve approach](#naive-approach) to the [resolver approach](#resolver-approach) outlined in this document. + +## Naïve approach + +The naïve approach multiplies the number of final values flatly with the number of alternate value layers. Mathematically, this can be expresed as the original starting number of tokens 𝑇, multiplied by layers of alternate values 𝐴𝑉, produces a final number of tokens 𝑇𝛥: + + + + T + × + AV + = + TΔ + + + + + +## Resolver approach + +The resolver approach involves breaking apart all tokens 𝑇 into subsets 𝑡1, 𝑡2, … 𝑡𝑛, and applying alternate value layers separately to produce a subtotal. The subtotals are added together to produce a final 𝑇𝛥 value. The key difference is avoiding flat multiplication across the entire superset by breaking into subsets. Mathematically this may be expressed like so: + + + + + + 𝑡1 + × + AV1 + + + = + + + TΔ1 + + + + + 𝑡2 + × + AV2 + + + = + + + TΔ2 + + + + ... + + + + 𝑡n + × + AVn + + + = + + + + T + Δn + + + + + + TΔ1 + + + TΔ2 + + + + TΔn + + = + TΔ + + + + + + +This illustrates the concept in abstract. See [syntax](#syntax) to see how it’s expressed in JSON. diff --git a/technical-reports/resolver/orthogonality.md b/technical-reports/resolver/orthogonality.md deleted file mode 100644 index 7aa5542..0000000 --- a/technical-reports/resolver/orthogonality.md +++ /dev/null @@ -1,26 +0,0 @@ -# Orthogonality - -Modifiers are considered **orthogonal** when they can be changed independently without affecting each other's resolution logic. - - - - diff --git a/technical-reports/resolver/resolution-aliasing.md b/technical-reports/resolver/resolution-aliasing.md deleted file mode 100644 index 76bd2f1..0000000 --- a/technical-reports/resolver/resolution-aliasing.md +++ /dev/null @@ -1,155 +0,0 @@ -# Resolution Aliasing - -Aliasing allows for dynamic namespacing or renaming of token paths during resolution. This is particularly useful when integrating external token sets or avoiding naming conflicts. - - - -**Example:** - - - - diff --git a/technical-reports/resolver/resolution-example.md b/technical-reports/resolver/resolution-example.md deleted file mode 100644 index 0f42c35..0000000 --- a/technical-reports/resolver/resolution-example.md +++ /dev/null @@ -1,199 +0,0 @@ -# Resolver Resolution Example - - diff --git a/technical-reports/resolver/resolution-logic.md b/technical-reports/resolver/resolution-logic.md index 3367a74..de9ada7 100644 --- a/technical-reports/resolver/resolution-logic.md +++ b/technical-reports/resolver/resolution-logic.md @@ -1,94 +1,416 @@ # Resolution Logic -The resolution process involves the following steps: +Tools MUST handle the resolution stages in this order to produce the correct output. + +1. [Input validation](#input-validation) +2. [Set flattening](#set-flattening) +3. [Modifier application](#modifier-application) +4. [Namespacing](#namespacing) +5. [Alias resolution](#alias-resolution): +6. [Resolution](#resolution-0) + +## Input validation + +Tools MUST require all [=inputs=] meet the schema described in that resolver’s [modifiers syntax](#modifiers). + +If a resolver does NOT declare any modifiers, skip this step and proceed to [Sets flattening](#sets-flattening) + +1. For every key in the input object: + 1. Verify it corresponds with a valid modifier. If it does not, throw an error. + 1. Verify that key’s value corresponds with that modifier’s allowed values. If it does not, throw an error. +2. For every modifier in the resolver: + 1. If that resolver does NOT declare a default value, verify a key is provided in the input. If not, throw an error. + +## Set flattening + +Tools MUST iterate over the resolver’s [sets syntax](#sets) in order. + +1. Starting with the first set: + 1. Load the first item in the `values` array to form the **basis** for all tokens. + 2. Load the next item in the `values` array, merging the objects together. + 3. Resolve conflicts according to [conflict resolution](#conflict-resolution). + 4. [Aliases](../format/#aliases-references) MUST NOT be resolved yet. That must happen at the end. Aliases MAY refer to values that will be supplied in upcoming steps. + 5. Continue loading the next item in the `values` array, repeating steps 2–3. + 6. After all `values` have been merged into the **basis** object, keep that in memory and continue onto the next set. +2. Continue onto the set, loading tokens in the same way as before. + 1. Repeating the steps previously, you’ll also end up with a **basis** for this set—a single object containing all tokens referenced in the set. +3. Continue one-by-one through the remaining sets, until you have a collection of one **basis** object per set. +4. Return back to the first set, first basis, then merge the second set, second basis, and so on, until you reach the final set. + 1. [Resolve conflicts](#conflict-resolution) as every additional set is merged. +5. After all sets have been merged, there will be one single tokens object containing all tokens referenced. + +### Conflict resolution + +Conflict resolution occurs when flattening [=sets=] or applying [=modifiers=], and a token name is occupied by tokens of different values, from different sources. In many cases, this is intentional, but not always. + +When 2 tokens try and occupy the same space, tools MUST resolve the conflict in the following manner: + +1. If the token types are **identical**, overwrite the latter value with the former. +1. If the token types are **incompatible**, the tool MUST throw an error. +1. If one namespace is a token, and the other is a group, the tool MUST throw an error. +1. If one value is an alias (i.e. the `$type` is unknown), overwrite the value. + + -4. **Aliasing and Namespacing**: Apply any aliasing specified in the modifiers to namespace or rename tokens. +## Modifier application - + -#### Input Validation + -- Load each token set in the order specified. -- Merge the tokens, with later sets overriding earlier ones on name conflicts. +## Alias resolution -#### Modifier Application +Alias resolution may only done after all [sets](#set-flattening) and [modifiers](#modifier-application) are handled, and there are no other tokens to merge in. Resolve aliases the same way as outlined in the [format](../format/#aliases-references), allowing deep aliases but erring and stopping resolution on circular aliases and/or aliases that point to unresolvable types (such as aliasing a [dimension token](#dimension) inside a [gradient token](#gradient), which is invalid). -- For each modifier: - - Apply any aliasing or namespacing specified in meta. - - Load the token sets associated with the selected modifier value. - - Merge these tokens with the base tokens, applying overrides as necessary. +## Resolution -#### Alias Resolution +After all aliases resolve correctly in the final set, the end result is one tokens object, that behaves as if it was a single JSON file to begin with. - +## Version + +MUST be `2025-10-01`. Reserved for future versions in case breaking changes are introduced. + +```json +{ + "name": "Marketing Design System", + "version": "2025-10-01" +} +``` + ## Description A resolver MAY provide additional information. @@ -34,6 +46,7 @@ A resolver MAY provide additional information. ```json { "name": "Marketing Design System", + "version": "2025-10-01", "description": "Last updated Summer 2025. Copyright Foo Corporation." } ``` From 5d3d973888cabee2ba172b156215470b49427496 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Wed, 27 Aug 2025 19:58:42 -0600 Subject: [PATCH 9/9] Resolver spec updates (#301) --- .../resolver/resolution-logic.md | 245 +++++++------ technical-reports/resolver/syntax.md | 322 ++++++------------ 2 files changed, 245 insertions(+), 322 deletions(-) diff --git a/technical-reports/resolver/resolution-logic.md b/technical-reports/resolver/resolution-logic.md index de9ada7..f38da63 100644 --- a/technical-reports/resolver/resolution-logic.md +++ b/technical-reports/resolver/resolution-logic.md @@ -3,11 +3,8 @@ Tools MUST handle the resolution stages in this order to produce the correct output. 1. [Input validation](#input-validation) -2. [Set flattening](#set-flattening) -3. [Modifier application](#modifier-application) -4. [Namespacing](#namespacing) -5. [Alias resolution](#alias-resolution): -6. [Resolution](#resolution-0) +2. [Token flattening](#token-flattening) +3. [Resolution](#resolution-0) ## Input validation @@ -21,48 +18,35 @@ If a resolver does NOT declare any modifiers, skip this step and proceed to [Set 2. For every modifier in the resolver: 1. If that resolver does NOT declare a default value, verify a key is provided in the input. If not, throw an error. -## Set flattening +## Token flattening -Tools MUST iterate over the resolver’s [sets syntax](#sets) in order. +Tools MUST iterate over the [tokens](#tokens) array in order. -1. Starting with the first set: - 1. Load the first item in the `values` array to form the **basis** for all tokens. - 2. Load the next item in the `values` array, merging the objects together. - 3. Resolve conflicts according to [conflict resolution](#conflict-resolution). - 4. [Aliases](../format/#aliases-references) MUST NOT be resolved yet. That must happen at the end. Aliases MAY refer to values that will be supplied in upcoming steps. - 5. Continue loading the next item in the `values` array, repeating steps 2–3. - 6. After all `values` have been merged into the **basis** object, keep that in memory and continue onto the next set. -2. Continue onto the set, loading tokens in the same way as before. - 1. Repeating the steps previously, you’ll also end up with a **basis** for this set—a single object containing all tokens referenced in the set. -3. Continue one-by-one through the remaining sets, until you have a collection of one **basis** object per set. -4. Return back to the first set, first basis, then merge the second set, second basis, and so on, until you reach the final set. - 1. [Resolve conflicts](#conflict-resolution) as every additional set is merged. -5. After all sets have been merged, there will be one single tokens object containing all tokens referenced. +### Sets -### Conflict resolution - -Conflict resolution occurs when flattening [=sets=] or applying [=modifiers=], and a token name is occupied by tokens of different values, from different sources. In many cases, this is intentional, but not always. - -When 2 tokens try and occupy the same space, tools MUST resolve the conflict in the following manner: - -1. If the token types are **identical**, overwrite the latter value with the former. -1. If the token types are **incompatible**, the tool MUST throw an error. -1. If one namespace is a token, and the other is a group, the tool MUST throw an error. -1. If one value is an alias (i.e. the `$type` is unknown), overwrite the value. +1. Sets are loaded in the [tokens](#tokens) array order. +1. If this uses [simple syntax](#simple-syntax) (string), treat it as if it is a set with no `name`, with a single item in the `sources` array. +1. Every token reference in `sources` MUST be resolved in array order. +1. For every token reference, merge with the previous set. +1. [Aliases](../format/#aliases-references) MUST NOT be resolved yet. That must happen at the end. Aliases MAY refer to values that will be supplied in upcoming steps. + + -## Modifier application +### Modifiers -Apply the selected [=modifiers=] based on the [=inputs=]. Modifiers can override tokens from the base sets or introduce new tokens. +Every modifier is applied in order, taking in the external input [=inputs=]. -1. For every modifier in the `modifiers` array, iterate in declaration order. - 1. For that modifier, load the corresponding [=input=] value. - 1. If there is not an input value, load `meta.default`. - 1. If there is no default value, throw an error and stop resolution. - 1. Load the [<token-defs>](#token-defs) array that corresponds to the input value, or default value. - 1. Apply namespacing if `meta.namespace` is declared. - 1. In array order, flatten each token using the same steps as [set flattening](#set-flattening). - 1. At the end of the array, merge into the basis object created in the previous [set flattening](#set-flattening) step. -1. Continue through all modifiers repeating the same process, merging into the basis each time. +1. For that modifier, load the corresponding [=input=] value. + 1. If there is not an input value, load the `default` context. + 1. If there is neither an input value nor `default` context, throw an error and stop resolution. +1. Load the context’s appropriate [<token-defs>](#token-defs) array that corresponds to the input value. +1. Flatten the [<token-defs>](#token-defs) array into the existing basis, in array order. +### Conflict resolution (flattening) + +Conflict resolution occurs when flattening [=sets=] or applying [=modifiers=], and a token name appears by tokens of different values, from different sources. In many cases, this is intentional, but not always. + +When 2 tokens try and occupy the same space, tools MUST resolve the conflict in the following manner: + +1. If the token types are **identical**, overwrite the latter value with the former. +1. If the token types are **incompatible**, the tool MUST throw an error. +1. If one name is a token, and the other is a group, the tool MUST throw an error. +1. If one value is an alias (i.e. the `$type` is unknown), overwrite the value. + ## Alias resolution Alias resolution may only done after all [sets](#set-flattening) and [modifiers](#modifier-application) are handled, and there are no other tokens to merge in. Resolve aliases the same way as outlined in the [format](../format/#aliases-references), allowing deep aliases but erring and stopping resolution on circular aliases and/or aliases that point to unresolvable types (such as aliasing a [dimension token](#dimension) inside a [gradient token](#gradient), which is invalid). @@ -210,21 +232,18 @@ Resolver ```json { - "sets": [ - { "name": "foundation", "values": ["foundation.json"] }, - { "values": ["components/button.json"] } - ], - "modifiers": [ + "tokens": [ { + "type": "set", + "sources": ["foundation.json"] + }, + "components/button.json", + { + "type": "modifier", "name": "theme", - "type": "enumerated", - "values": [ - { "name": "light", "values": ["themes/light.json"] }, - { "name": "dark", "values": ["themes/dark.json"] } - ], - "meta": { - "default": "light", - "namespace": "theme" + "context": { + "light": ["themes/light.json"], + "dark": ["themes/dark.json"] } } ] @@ -294,13 +313,15 @@ themes/dark.json ```json { - "accent": { - "$value": { - "colorSpace": "srgb", - "components": [0, 1, 0], - "hex": "#00ff00" - }, - "$type": "color" + "theme": { + "accent": { + "$value": { + "colorSpace": "srgb", + "components": [0, 1, 0], + "hex": "#00ff00" + }, + "$type": "color" + } } } ``` @@ -310,10 +331,11 @@ themes/dark.json 1. Input Validation 1. Verify that `theme` is a defined modifier (it passes). 2. Verify that `dark` is a valid value for the `theme` modifier (it passes). -2. Set flattening +2. Tokens flattening + + 1. The first item is a set containing `["foundation.json"]` in `sources`. Load these in array order.. + 2. The second item is a set containing `"components/button.json"`. Load that, and merge with the previous result, resulting in: - 1. Load `foundation.json` and `components/button.json`. File paths MUST be resolved relative to the location of the resolver file. - 2. Flattening all sets resuls in: ```json { "color": { @@ -340,32 +362,54 @@ themes/dark.json } ``` -3. Modifier application + 3. The third item is a modifier with the name `theme`. - 1. Apply the `theme` modifier with value `dark`. - 2. Load `themes/dark.json` - 3. Apply namespacing as per `meta.namespace` ("theme"), resulting in: + 1. Taking the `{ "theme": "dark" }` input results in `["themes/dark.json"]`. + 1. Load that, and flatten with the previous result, resulting in: - ```json - { - "theme": { - "accent": { - "$value": { - "colorSpace": "srgb", - "components": [0, 1, 0], - "hex": "#00ff00" - }, - "$type": "color" - } - } - } - ``` + ```json + { + "color": { + "brand": { + "primary": { + "$value": { + "colorSpace": "srgb", + "components": [1, 0, 0], + "hex": "#ff0000" + }, + "$type": "color" + } + } + }, + "theme": { + "accent": { + "$value": { + "colorSpace": "srgb", + "components": [0, 1, 0], + "hex": "#00ff00" + }, + "$type": "color" + } + }, + "button": { + "background": { + "$value": "{theme.accent}" + }, + "padding": { + "$value": { "value": 8, "unit": "px" }, + "$type": "dimension" + } + } + } + ``` + +3. Alias resolution -4. Alias resolution + 1. After all tokens have been loaded, `{theme.accent}` may now be resolved. - 1. Resolve `{theme.accent}` in `button.background`. +4. Resolution -5. Resolution. The final tokens will take the shape of: + 1. The final result, with the aliases applied, results in: ```json { @@ -408,9 +452,10 @@ themes/dark.json } ``` -Key highlights: +Key takeaways: -- The `accent` token was renamed to `theme.accent` because of the `meta.namespace` value. -- Without a resolver, `button.background` would have an invalid alias since `theme.accent` is not in the same file. +- Without the resolver, `{theme.accent}` is an invalid alias since it exited in a remote file. +- Sets and modifiers are applied in declaration order. +- The `theme` modifier did not have a `default` value. Therefore an error would have been thrown without an input. diff --git a/technical-reports/resolver/syntax.md b/technical-reports/resolver/syntax.md index 108436a..e769766 100644 --- a/technical-reports/resolver/syntax.md +++ b/technical-reports/resolver/syntax.md @@ -4,13 +4,14 @@ A resolver is a JSON object with the following properties: -| Name | Type | Required | Description | -| :------------------------------ | :----------------------- | :------: | :------------------------------------------------------ | -| [**name**](#name) | `string` | | A short, human-readable name for the resolver. | -| [**version**](#version) | `YYYY-MM-DD` | Y | Version, expressed as a ISO 8601 date. | -| [**description**](#description) | `string` | | Additional information about the resolver’s purpose. | -| [**sets**](#sets) | [Set[]](#set) | Y | Array of token subsets used as the base for resolution. | -| [**modifiers**](#modifiers) | [Modifier[]](#modifiers) | | Array of modifiers for use in different contexts. | +| Name | Type | Required | Description | +| :------------------------------ | :------------------ | :------: | :--------------------------------------------------- | +| [**name**](#name) | `string` | | A short, human-readable name for the resolver. | +| [**version**](#version) | `YYYY-MM-DD` | Y | Version, expressed as a ISO 8601 date. | +| [**description**](#description) | `string` | | Additional information about the resolver’s purpose. | +| [**tokens**](#tokens) | (Set \| Modifier)[] | Y | Resolution order of tokens. | + +Users SHOULD name resolver files with a `.resolver.json` syntax. ## Name @@ -53,38 +54,51 @@ A resolver MAY provide additional information. -## Sets +## Tokens + +The tokens key is an array that may contain any combination of [sets](#set) and [modifiers](#modifier). The order is significant, with tokens later in the array overriding any tokens that came before them, in case of conflict. + + -A resolver MUST provide an array of sets that combine to form the minimum set of design tokens. A set is an object with the following properties: +### Set -| Name | Type | Required | Description | -| :--------- | :-------------------------------- | :------: | :----------------------------------- | -| **name** | `string` | | An optional identifier for this set. | -| **values** | [<token-defs>](#token-defs) | Y | The tokens that belong to this set. | +| Name | Type | Required | Description | +| :---------- | :-------------------------------- | :------: | :----------------------------------------- | +| **type** | `"set"` | Y | MUST be `"set"`. | +| **name** | `string` | | Optional human-readable name for this set. | +| **sources** | [<token-defs>](#token-defs) | Y | The tokens that belong to this set. | -