Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: JSON Schema $ref for aliases (breaking change) #259

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 100 additions & 15 deletions technical-reports/format/aliases.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,125 @@
# Aliases / references
# Aliases (References)

Instead of having explicit values, tokens can reference the value of another token. To put it another way, a token can be an alias for another token. This spec considers the terms "alias" and "reference" to be synonyms and uses them interchangeably.

Aliases are useful for:

- Expressing design choices
- Eliminating repetition of values in token files (DRYing up the code)
- Eliminating repetition of values in token files (<abbr title="Don’t Repeat Yourself">DRY</abbr>ing up the code)

For a design token to reference another, its value MUST be a string containing the period-separated (`.`) path to the token it's referencing enclosed in curly brackets.
At any part of the schema, you MAY refer to another part of the schema with an object that has a `$ref` key and a valid [JSON pointer string](https://datatracker.ietf.org/doc/html/rfc6901). This syntax is borrowed from [JSON Schema (2020-12)](https://json-schema.org/draft/2020-12/json-schema-core#name-schema-references).

For example:
### Referencing Tokens

You MAY reference values in the same file by starting with the fragment character (`#`) followed by the token path, separated by forward slashes:

<aside class="example">

```json
{
"color": {
"blue": {
"4": { "$value": "#218bff", "$type": "color" }
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: color syntax is waiting on #257, but can be updated here if necessary. It’s unrelated to this proposal.

"brand": {
"$description": "Brand color",
"$ref": "#/color/blue/4"
}
}
}
```

`color.brand` aliases `color.blue.4`, and SHOULD always share its value (even at runtime).

</aside>

### Aliasing Non-tokens

It’s not just tokens that may be referenced; any part of the document may be referenced. For example, groups:

<aside class="example">

```json
{
"color": {
"gray": {
"1": { "$value": "#101010", "$type": "color" },
"2": { "$value": "#202020", "$type": "color" },
"3": { "$value": "#404040", "$type": "color" },
"4": { "$value": "#808080", "$type": "color" }
},
"grey": { "$ref": "#/color/gray" }
}
}
```

In this example, `color.grey.1` would be an alias for `color.gray.1`, `color.grey.2` would alias `color.gray.2`, etc.

</aside>

Any group or property (`$type`, `$value`, `$description`, `$extension`, etc.) may all be referenced, so long as the final resolved value results in a valid schema.

### Aliasing Tokens in Other Files

Aliasing tokens from other files is possible by specifing a URL, either relative (`./relative/path.json`) or absolute (`https://example.com/api/v1/tokens.json`).

To only use part of a JSON structure, you MAY use the fragment character (`#`) after the URL, followed by [the token path](#referencing-tokens).

<aside class="example">

```json
{
"group name": {
"token name": {
"$value": 1234,
"$type": "number"
"space": {
"sm": {
"$description": { "$ref": "./shared.json#/space/sm/$description" },
"$value": { "$ref": "./shared.json#/space/sm/$value" },
"$type": { "$ref": "./shared.json#/space/sm/$type" }
}
},
"alias name": {
"$value": "{group name.token name}"
"typography": {
"$ref": "https://example.com/api/v1/tokens/typography.json"
}
}
```

</aside>

When a tool needs the actual value of a token it MUST resolve the reference - i.e. lookup the token being referenced and fetch its value. In the above example, the "alias name" token's value would resolve to 1234 because it references the token whose path is `{group name.token name}` which has the value 1234.
If there is no `#` character, the entire file will be loaded. So this means files MAY contain partial or incomplete schemas. The same rules apply—it MUST resolve to a valid, complete schema in the end.

Tools SHOULD preserve references and therefore only resolve them whenever the actual value needs to be retrieved. For instance, in a [=design tool=], changes to the value of a token being referenced by aliases SHOULD be reflected wherever those aliases are being used.
## Additional Info

Aliases MAY reference other aliases. In this case, tools MUST follow each reference until they find a token with an explicit value. Circular references are not allowed. If a design token file contains circular references, then the value of all tokens in that chain is unknown and an appropriate error or warning message SHOULD be displayed to the user.
When a tool needs the actual value of a token it MUST resolve all aliases and references, i.e. lookup the token being referenced and fetch its value.

Tooling MUST allow `$ref` to have sibling values that act as overrides. In case of a conflict, tooling MUST keep the sibling values to `$ref`, i.e. `$ref` is resolved first, then any sibling keys are applied.

<p class="ednote" title="JSON Pointer syntax">
The format editors are currently researching JSON Pointer syntax to inform the exact syntax for aliases in tokens. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-5">https://datatracker.ietf.org/doc/html/rfc6901#section-5</a>
<p class="ednote" title="JSON Schema 2020-12">
JSON Schema 2020-12 was chosen over 2019-09 because it allows sibling overrides alongside `$ref`.
</p>

Tools SHOULD preserve all references, and only resolve them whenever the actual value needs to be retrieved.

<aside class="example">

For example, if a token named `color.text.primary` was an alias of `color.palette.black`, the following CSS SHOULD be generated:

##### ✅ Recommended

```css
:root {
--color-palette-black: #000000;
--color-text-primary: var(--color-palette-black);
}
```

##### ❌ Not Recommended

```css
:root {
--color-palette-black: #000000;
--color-text-primary: #000000;
}
```

</aside>

Aliases MAY reference other aliases. In this case, tools MUST follow each reference until they find a token with an explicit value. Circular references are not allowed. If a design token file contains circular references, then the value of all tokens in that chain is unknown and an appropriate error or warning message SHOULD be displayed to the user.
29 changes: 16 additions & 13 deletions technical-reports/format/composite-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Every shadow style has the exact same parts (color, X & Y offsets, etc.), but th
Specifically, a composite type has the following characteristics:

- Its value is an object or array, potentially containing nested objects or arrays, following a pre-defined structure where the properties of the (nested) object(s) or the elements of the (nested) arrays are sub-values.
- Sub-values may be explicit values (e.g. `"#ff0000"`) or references to other design tokens that have sub-value's type (e.g. `"{some.other.token}"`).
- Sub-values may be explicit values (e.g. `"#ff0000"`) or references to other design tokens that have sub-value's type (e.g. `{ "$ref": "#/some/other/token" }`).

A design token whose type happens to be a composite type is sometimes also called a composite (design) token. Besides their type, there is nothing special about composite tokens. They can have all the other additional properties like [`$description`](#description) or [`$extensions`](#extensions). They can also be referenced by other design tokens.

Expand Down Expand Up @@ -53,9 +53,9 @@ A design token whose type happens to be a composite type is sometimes also calle
"$type": "shadow",
"$description": "A composite token where some sub-values are references to tokens that have the correct type and others are explicit values",
"$value": {
"color": "{color.shadow-050}",
"offsetX": "{space.small}",
"offsetY": "{space.small}",
"color": { "$ref": "#/color/shadow-050" },
"offsetX": { "$ref": "#/space/small" },
"offsetY": { "$ref": "#/space/small" },
"blur": { "value": 1.5, "unit": "rem" },
"spread": { "value": 0, "unit": "rem" }
}
Expand All @@ -65,8 +65,8 @@ A design token whose type happens to be a composite type is sometimes also calle
"component": {
"card": {
"box-shadow": {
"$description": "This token is an alias for the composite token {shadow.medium}",
"$value": "{shadow.medium}"
"$description": "This token is an alias for the composite token #/shadow/medium",
"$value": { "$ref": "#/shadow/medium" }
}
}
}
Expand Down Expand Up @@ -160,7 +160,10 @@ Object stroke style values MUST have the following properties:
"notification-border-style": {
"$type": "strokeStyle",
"$value": {
"dashArray": ["{dash-length-medium}", { "value": 0.25, "unit": "rem" }],
"dashArray": [
{ "$ref": "#/dash-length-medium" },
{ "value": 0.25, "unit": "rem" }
],
"lineCap": "butt"
}
},
Expand Down Expand Up @@ -230,7 +233,7 @@ Represents a border style. The `$type` property MUST be set to the string `borde
"focusring": {
"$type": "border",
"$value": {
"color": "{color.focusring}",
"color": { "$ref": "#/color/focusring" },
"width": {
"value": 1,
"unit": "px"
Expand Down Expand Up @@ -439,12 +442,12 @@ Describes a gradient that is solid yellow for the first 2/3 and then fades to re
"position": 0
},
{
"color": "{brand-primary}",
"color": { "$ref": "#/brand-primary" },
"position": 0.5
},
{
"color": "#000000",
"position": "{position-end}"
"position": { "$ref": "#/position-end" }
}
]
}
Expand Down Expand Up @@ -495,9 +498,9 @@ Represents a typographic style. The `$type` property MUST be set to the string `
"microcopy": {
"$type": "typography",
"$value": {
"fontFamily": "{font.serif}",
"fontSize": "{font.size.smallest}",
"fontWeight": "{font.weight.normal}",
"fontFamily": { "$ref": "#/font/serif" },
"fontSize": { "$ref": "#/font/size/smallest" },
"fontWeight": { "$ref": "#/font/weight/normal" },
"letterSpacing": {
"value": 0,
"unit": "px"
Expand Down
31 changes: 25 additions & 6 deletions technical-reports/format/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ These definitions are focused on the technical aspects of the specification, aim

## (Design) Token

A (Design) Token is information associated with a human readable name, at minimum a name/value pair.
A (Design) Token is information associated with a human readable name, at minimum a name/value pair.

For example:

Expand Down Expand Up @@ -104,14 +104,33 @@ Groups are arbitrary and tools SHOULD NOT use them to infer the type or purpose

A design token's value can be a reference to another token. The same value can have multiple names or _aliases_.

The following Sass example illustrates this concept:
The following CSS example illustrates this concept:

```scss
$color-palette-black: #000000;
$color-text-primary: $color-palette-black;
```css
:root {
--color-palette-black: #000000;
--color-text-primary: var(--color-palette-black);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I also updated this earlier section introducing the term “Alias,” and used CSS variables rather than Sass variables. I think it better preserves this idea we want to keep—of aliases carrying through to runtime better. Plus I think in 2024, CSS variables are more widely-used than Sass variables (completely guessing; I have no data to back this up).

}
```

The value of `--color-text-primary` is `#000000`, because `--color-text-primary` _references `--color-palette-black`_. We can also say `--color-text-primary` is an _alias_ for `--color-palette-black.`

In JSON notation, we define an alias the same way as [JSON Schema references](https://json-schema.org/draft/2020-12/json-schema-core#name-schema-references):

```json
{
"color": {
"palette": {
"black": { "$value": "#000000", "$type": "color" }
},
"text": {
"primary": { "$ref": "#/color/palette/black" }
}
}
}
```

The value of `$color-text-primary` is `#000000`, because `$color-text-primary` _references `$color-palette-black`_. We can also say `$color-text-primary` is an _alias_ for `$color-palette-black.`
To learn more about syntax and uses, see [the definition on Aliases](#aliases-references).

## Composite (Design) Token

Expand Down
Loading