[community-group] Closed Pull Request: Proposal: JSON Schema $ref for aliases (breaking change)

drwpow has just closed drwpow's pull request 259 for https://github.com/design-tokens/community-group:

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

This makes a breaking change to aliases, changing the syntax:

```diff
- { "$value": "{color.blue.01}" }
+ { "$value": { "$ref": "#/color/blue/01/$value" } }
```

This provides a solution for #166, as well as aligns the DTCG format closer to common syntax that address similar problems.

## Reasoning

DTCG users have kept their files separately for a long time, and have asked for the ability to reference tokens in other files (#166). Since we were already borrowing from [JSON Schema](https://json-schema.org/draft/2020-12/json-schema-core#name-schema-references) in places (primarily the `$` character to mark reserved keys), this brings JSON Schema’s `$ref` keyword to the concept of token aliasing.

The `$ref` keyword (aka “JSON pointers”) comes both from JSON Schema and the [OpenAPI specification](https://spec.openapis.org/oas/latest.html) (previously _Swagger_), and has been in use for over a decade. It is syntactically already consistent with the DTCG format, and functionally consistent as well. Most languages have lots of existing tooling to parse and resolve these (e.g. [JavaScript](https://www.npmjs.com/search?q=json%20pointer)).

For the DTCG format, it is a way to keep all the functionality of aliases while extending it for future needs, borrowing from prior art where this syntax is known, used, and well-defined. For simplicity, this proposal aims to **replace** the previous alias syntax, rather than keep 2 conflicting ways to accomplish the same thing (which will be expanded on in points below).

### What’s gained

- The ability for tokens to alias tokens in remote files, supported by the spec
- The ability for tokens to reuse any part of other tokens (still bound by the requirement it must ultimately _resolve_ to a valid schema)

### What’s lost

- Nothing! This is backwards-compatible (behavior-wise), and the previous alias syntax can be cleanly upgraded with no change in behavior.

### Pros

- This greatly expands what’s possible with the DTCG format, now that any part of the document (or remote documents, or _partial_ remote documents) may be reused.
  - For example, aliased tokens could now even reuse parts of `$extensions` if they wanted to, while selectively applying overrides
  - Because it allows overrides, you can reuse more DTCG syntax than you could before, even sub-schemas in remote documents (e.g. extending public design systems)
- While this is flagged as a breaking _syntax_ change, it’s a backwards compatible _functionality_ upgrade to aliases. There is no end-behavior nor features that should be lost with this change.
- Figma Styles and Variables use the `/` character in names, so now token names will map 1:1
  - _Are there other cases where this is true?_
- This is backwards compatible for token names. Tokens can still use `/` and `#` characters in their names; they just have to be escaped with `~0` and `~1` respectively ONLY INSIDE `$ref`, according to the [spec](https://datatracker.ietf.org/doc/html/rfc6901#section-3):

   ```json
   {
     "my/token": { "$type": "number", "$value": 5 },
     "other-token": { "$ref": "#/my~1token" }
   }
   ```
- For DTCG parsers, this simplifies parsing/handling of aliases. Consider the old syntax:
   ```json
   {
     "font-1": { "$type": "fontFamily", "$value": "Inter" },
     "font-2": { "$type": "fontFamily", "$value": "{font-1}" }
   }
   ```

   When a schema isn’t distinguishable by structure, tool makers have to do additional work string matching to do basic typecasting and discrimination.
- Tool makers also benefit from over a decade of prior art and tooling for handling this syntax (even if they may not have used it directly before)

### Cons

- Given that aliases are core syntax, this is a disruptive change, not just for toolmakers but for consumers of the DTCG spec. We likely would want to talk about a deprecation strategy
- It does change the structure, converting a string node (`"{my.token.alias"}`) to an object (`{ "$ref": "#/my/token/alias" }`). Again, though long-term it will be easier to work with, short-term will impose migration pains.
- It is more verbose, especially requiring repeat of `$value`  e.g.:
    ```diff
    - "$value": "{color.blue.500}"
    + "$value": { "$ref": "#/color/blue/500/$value" }
    ```
- It does require some extra explanation around “what counts as an alias” vs what doesn’t ([see comment](https://github.com/design-tokens/community-group/pull/259#issuecomment-2631976574))
  - **Proposal:** count anything as an alias if a `$value` of one token points to another token’s `$value` (which means aliasing the entire token object itself, or a group, means aliases are created)
   
### Alternatives

- We could extend the current `{…}` alias syntax to support remote files. But I think realistically, it would follow the JSON Pointer spec underneath. But that would require extensive documentation to outline this minor detail, and would still leave toolmakers without the ability to use any of the myriad tools available for JSON pointers. If we’re adhering to those principles underneath, why not just use the syntax directly?
- Those familiar with JSON Schema will also be familiar with its counterpart [`$defs`](https://json-schema.org/draft/2020-12/json-schema-core#name-schema-re-use-with-defs), which allows you to declare reusable parts of a document that can be `$ref`’d anywhere (but by default are ignored/not parsed). $defs are NOT being proposed here, because it doesn’t solve an immediate problem, and introduces more complexity than necessary (but could be an additive followup)
- An alternate idea is to keep _aliases_ the same `{color.blue.05}`, and introduce this new concept as a _reference_ (and distinguish between the terms). Aliases would just be the “legacy” way to declare token aliases. I didn’t initially propose this because:
  - There were no advantages I could find. Even the automatic `$type` inheritance is possible with `$ref`, but `$ref` carries more benefits
  - I couldn’t think of a sensible usecase of distinguishing “aliases” from “references” after reading this note in the [JSON Schema spec (2019-09](https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02#rfc.appendix.B.2):
  
    > Attempting to remove all references and produce a single schema document does not, in all cases, produce a schema with identical behavior to the original form.

    In other words, even the concept of `$ref` feels spiritally identical to the existing DTCG alias syntax—there are times when you _do_ want to preserve and reference those values later, and they are significant in some ways (even including overrides).
    
  However, I think we could define some sort of deprecation strategy where the old syntax is supported for some time before switching over.

## Notes

- This does introduce some confusion in token IDs. For example, is the “official” token ID now `color.blue.05` or `color/blue/05`?
  - Further, does this mean `.` is allowed in token names again?
- As someone that’s worked extensively with JSON Schema syntax, it’s often not enough to say “follow the spec” because there are, like, dozens of conflicting versions that all have breaking changes. So I’m proposing specifically the [2020-12](https://json-schema.org/draft/2020-12/json-schema-core) version, in the case that tool makers run into one of these conflicts
  - Specifically, `2020-12` DOES allow `$ref` to have sibling keys (“overrides”), which is **IMPORTANT**! Without this, I believe this would lose some functionality of aliases—specifically their ability to inherit `$type` automatically.
  - 99.9% of the time it’s not an issue; this is just coverage for that one weird edge case 😅 

_Edit: an earlier version referenced the upcoming Resolver Spec proposal, but I’ve realized the two aren’t related, and Resolver Spec isn’t a motivator for this. Mentions have been removed._

See https://github.com/design-tokens/community-group/pull/259


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Sunday, 5 October 2025 21:04:40 UTC