Design Tokens Resolver Module

Draft Community Group Report

Latest published version:
https://www.w3.org/design-tokens-resolvers/
Latest editor's draft:
https://design-tokens.github.io/community-group/
Editors:
Andrew L'Homme
Drew Powers
Esther Cheran
James Nash
Joren Broekema
Louis Chenais
Mike Kamminga
Feedback:
GitHub design-tokens/community-group (pull requests, new issue, open issues)

Abstract

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.

Status of This Document

This specification was published by the Design Tokens Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

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.

GitHub Issues are preferred for discussion of this specification.

1. Introduction

This section is non-normative.

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 valueswhile producing the fewest minimal end number. We’ll compare a naïve approach to the resolver approach outlined in this document.

1.1 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Δ

1.2 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 to see how it’s expressed in JSON.

2. Terminology

2.1 Resolver

The mechanism by which multiple possible values of design tokens are reduced to a single value, i.e. this module.

2.2 Set

A subset of all design tokens that collectively form the default superset. Implementors SHOULD ensure the sum total of sets contain mutually exclusive tokens, i.e. they don’t overwrite one another and may be combined in any order to produce the same result.

2.3 Modifier

A subset of all design tokens that provide alternate values. Modifiers MAY take an input to be used in providing alternate values.

2.4 Input

The user’s selection for the modifiers, expressed as a key–value map. See example.

2.5 Alternate value

The property of tokens to express different values under different conditions. “Light mode” and “dark mode” are examples of alternate values.

Issue 1

2.6 Resolution

The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens.

Issue 2
Issue 3

2.7 Orthogonal (orthogonality)

The characteristic of modifiers that do not overlap with one another, i.e. operate on different tokens. Modifiers MAY be orthogonal, but it are not required to be.

3. Syntax

3.1 Resolver file

A resolver is a JSON object with the following properties:

Name Type Required Description
name string A short, human-readable name for the resolver.
description string Additional information about the resolver’s purpose.
sets Set[] Y Array of token subsets used as the base for resolution.
modifiers Modifier[] Array of modifiers that may provide alternate values.

3.2 Name

A resolver MAY provide a human-readable name. This is used to identify the resolver.

3.3 Description

A resolver MAY provide additional information.

3.4 Sets

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:

Name Type Required Description
name string An optional identifier for this set.
values <token-defs> Y The tokens that belong to this set.
Editor's note: Proposal

3.5 Modifiers

A resolver MAY provide an array of modifiers that extend, append, and/or override the final token values. A modifier is an object with the following properties:

Name Type Required Description
name string Y The name of the modifier.
type "enumerated" | "include" The type of modifier (default: enumerated)

3.5.1 Enumerated type

An enumerated modifier adds the following additional properties:

Name Type Required Description
values Input[] Y The inputs required for this modifier.
meta Object Additional data for this modifier
meta.default string Declare the default value of the input, if none is provided.
meta.namespace string Namespace all tokens with a valid token name.

An enumerated modifier that allows users to omit an input value MUST specify a default value.

Editor's note

3.5.2 Include type

An include modifier adds the following additional properties:

Name Type Required Description
values Input[] Y The inputs required for this modifier.
meta Object Additional data for this 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.

Editor's note

3.5.3 Inputs

An input is a mapping of modifier names to modifier values declared in any resolver. Inputs are not part of the resolver file itself, rather, provided to the tool alongside the resolver. A resolver that declares any modifiers MUST be consumed with an input as options.

An input SHOULD be serializable to a JSON object. Meaning, an input MAY be expressed in any programming language, but that expression should be easily converted back into a JSON object. Related concepts would include an object in JavaScript or a dictionary in Python.

Tools that load a resolver that declares modifiers SHOULD throw an error if an accompanying input is not provided.

3.5.4 Namespacing

Namespacing allows for automatic prefixing of token names

Issue 4

Example:

Issue 5

3.5.5 Orthogonality

Modifiers are said to be orthogonal when they do not operate on the same set of tokens. In practical terms, if modifiers are orthogonal, then the order in which they are applied isn’t significant since they will produce the same values.

Implementors SHOULD make modifiers orthogonal. Tools MAY decide how to handle non-orthogonal modifiers.

3.6 <token-defs>

An array consisting of:

Any other inputs are invalid.

The array order is significant, where tokens with the same name overwrite previous instances of that token, if any.

3.7 $extensions

An $extensions object MAY be added to any set, modifier, or object in this spec to declare arbitrary metadata ignored by tooling. Its purpose in a resolver is the same as in the format.

4. Resolution Logic

Tools MUST handle the resolution stages in this order to produce the correct output.

  1. Input validation
  2. Set flattening
  3. Modifier application
  4. Namespacing
  5. Alias resolution:
  6. Resolution

4.1 Input validation

Tools MUST require all inputs meet the schema described in that resolver’s modifiers syntax.

If a resolver does NOT declare any modifiers, skip this step and proceed to 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.
    2. 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.

4.2 Set flattening

Tools MUST iterate over the resolver’s sets syntax 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.
    4. Aliases 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 as every additional set is merged.
  5. After all sets have been merged, there will be one single tokens object containing all tokens referenced.

4.2.1 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.
  2. If the token types are incompatible, the tool MUST throw an error.
  3. If one namespace is a token, and the other is a group, the tool MUST throw an error.
  4. If one value is an alias (i.e. the $type is unknown), overwrite the value.

4.3 Modifier application

Apply the selected modifiers based on the inputs. Modifiers can override tokens from the base sets or introduce new tokens.

  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.
      2. If there is no default value, throw an error and stop resolution.
    2. Load the <token-defs> array that corresponds to the input value, or default value.
    3. Apply namespacing if meta.namespace is declared.
    4. In array order, flatten each token using the same steps as set flattening.
    5. At the end of the array, merge into the basis object created in the previous set flattening step.
  2. Continue through all modifiers repeating the same process, merging into the basis each time.
Issue 6

4.4 Alias resolution

Alias resolution may only done after all sets and modifiers are handled, and there are no other tokens to merge in. Resolve aliases the same way as outlined in the format, 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 inside a gradient token, which is invalid).

4.5 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.

5. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MAY, MUST, MUST NOT, and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

Tools implementing the Resolver Specification MUST:

A. Acknowledgments

This section is non-normative.

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.

B. References

B.1 Normative references

[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc8174