@adobe/design-data-spec 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +3 -3
  2. package/components/button.json +70 -0
  3. package/conformance/README.md +0 -1
  4. package/conformance/invalid/SPEC-016/expected-errors.json +10 -0
  5. package/conformance/invalid/SPEC-016/tokens.tokens.json +8 -0
  6. package/conformance/invalid/SPEC-017/expected-errors.json +10 -0
  7. package/conformance/invalid/SPEC-017/tokens.tokens.json +7 -0
  8. package/conformance/invalid/SPEC-018/dataset.json +9 -0
  9. package/conformance/invalid/SPEC-018/expected-errors.json +10 -0
  10. package/conformance/invalid/SPEC-019/dataset.json +29 -0
  11. package/conformance/invalid/SPEC-019/expected-errors.json +10 -0
  12. package/conformance/invalid/SPEC-020/dataset.json +27 -0
  13. package/conformance/invalid/SPEC-020/expected-errors.json +10 -0
  14. package/conformance/invalid/SPEC-021/dataset.json +18 -0
  15. package/conformance/invalid/SPEC-021/expected-errors.json +10 -0
  16. package/conformance/invalid/SPEC-022/dataset.json +33 -0
  17. package/conformance/invalid/SPEC-022/expected-errors.json +10 -0
  18. package/conformance/invalid/SPEC-023/dataset.json +18 -0
  19. package/conformance/invalid/SPEC-023/expected-errors.json +10 -0
  20. package/conformance/invalid/SPEC-024/dataset.json +18 -0
  21. package/conformance/invalid/SPEC-024/expected-errors.json +10 -0
  22. package/conformance/valid/component-refs/dataset.json +63 -0
  23. package/conformance/valid/composite-drop-shadow.json +14 -0
  24. package/conformance/valid/composite-typography-scale.json +6 -0
  25. package/conformance/valid/composite-typography.json +12 -0
  26. package/conformance/valid/string-name-escape-hatch.json +7 -0
  27. package/fields/scaleIndex.json +15 -0
  28. package/package.json +18 -6
  29. package/rules/rules.yaml +113 -5
  30. package/schemas/anatomy-part.schema.json +35 -0
  31. package/schemas/component.schema.json +267 -0
  32. package/schemas/field.schema.json +2 -2
  33. package/schemas/state-declaration.schema.json +36 -0
  34. package/schemas/token.schema.json +26 -10
  35. package/schemas/value-types/drop-shadow.schema.json +20 -0
  36. package/schemas/value-types/typography-scale.schema.json +13 -0
  37. package/schemas/value-types/typography.schema.json +16 -0
  38. package/spec/agent-surface.md +116 -0
  39. package/spec/anatomy-format.md +167 -0
  40. package/spec/component-format.md +326 -0
  41. package/spec/evolution.md +0 -1
  42. package/spec/index.md +27 -21
  43. package/spec/state-model.md +245 -0
  44. package/spec/token-format.md +80 -15
  45. package/src/canonical.js +61 -0
  46. package/src/validate.js +166 -0
package/spec/index.md CHANGED
@@ -11,12 +11,15 @@ The specification defines:
11
11
 
12
12
  1. **Token format** — structured token identity (`name`), literal `value` or alias `$ref`, and lifecycle metadata ([Token format](token-format.md)).
13
13
  2. **Taxonomy** — concept categories, token term vocabulary, formatting style, and the distinction between component anatomy and token objects ([Taxonomy](taxonomy.md)).
14
- 3. **Cascade and resolution** — layered data (foundation, platform, product), specificity, and how a context picks a winning value ([Cascade](cascade.md)).
15
- 4. **Dimensions** — declared modes, defaults, and coverage expectations ([Dimensions](dimensions.md)).
16
- 5. **Platform manifest** — how a platform repo pins foundation data, filters tokens, and applies typed overrides ([Manifest](manifest.md)).
17
- 6. **Semantic diff** — change taxonomy, token identity rules, and property-level change tracking for comparing dataset versions ([Diff](diff.md)).
18
- 7. **Query notation** — filter syntax for selecting tokens by structured fields ([Query](query.md)).
19
- 8. **Evolution** — deprecation lifecycle, migration windows, change classification, and legacy format contract ([Evolution](evolution.md)).
14
+ 3. **Component format** — component declaration shape: API options, named content slots, anatomy parts, state model, and cross-reference validation rules ([Component format](component-format.md)).
15
+ 3a. **Anatomy format** — anatomy part declaration shape: field constraints, canonical anatomy vocabulary, and cross-reference rules for token `anatomy` field values ([Anatomy format](anatomy-format.md)).
16
+ 3b. **State model** — state declaration shape: trigger semantics, precedence and resolution algorithm, canonical state vocabulary, and cross-reference rules for token `state` field values ([State model](state-model.md)).
17
+ 4. **Cascade and resolution** — layered data (foundation, platform, product), specificity, and how a context picks a winning value ([Cascade](cascade.md)).
18
+ 5. **Dimensions** — declared modes, defaults, and coverage expectations ([Dimensions](dimensions.md)).
19
+ 6. **Platform manifest** — how a platform repo pins foundation data, filters tokens, and applies typed overrides ([Manifest](manifest.md)).
20
+ 7. **Semantic diff** — change taxonomy, token identity rules, and property-level change tracking for comparing dataset versions ([Diff](diff.md)).
21
+ 8. **Query notation** — filter syntax for selecting tokens by structured fields ([Query](query.md)).
22
+ 9. **Evolution** — deprecation lifecycle, migration windows, change classification, and legacy format contract ([Evolution](evolution.md)).
20
23
 
21
24
  ## Conformance
22
25
 
@@ -50,16 +53,19 @@ Full governance (compatibility tiers, migration, CLI `--spec-version`) is discus
50
53
 
51
54
  ## Normative references (sibling documents)
52
55
 
53
- | Document | Role |
54
- | ------------------------------- | ---------------------------------------------------------------- |
55
- | [Token format](token-format.md) | Token `name`, `value` / `$ref`, value types, lifecycle metadata. |
56
- | [Taxonomy](taxonomy.md) | Concept categories, vocabulary, formatting, anatomy vs objects. |
57
- | [Cascade](cascade.md) | Layers, specificity, resolution algorithm. |
58
- | [Dimensions](dimensions.md) | Dimension declarations, built-in dimensions, coverage. |
59
- | [Manifest](manifest.md) | Platform manifest fields and validation expectations. |
60
- | [Diff](diff.md) | Semantic diff change taxonomy, token identity, property changes. |
61
- | [Query](query.md) | Filter notation for selecting tokens by structured fields. |
62
- | [Evolution](evolution.md) | Deprecation lifecycle, migration windows, change classification. |
56
+ | Document | Role |
57
+ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
58
+ | [Token format](token-format.md) | Token `name`, `value` / `$ref`, value types, lifecycle metadata. |
59
+ | [Taxonomy](taxonomy.md) | Concept categories, vocabulary, formatting, anatomy vs objects. |
60
+ | [Component format](component-format.md) | Component declaration: options, slots, anatomy (→ anatomy-format.md), states (→ state-model.md), lifecycle. |
61
+ | [Anatomy format](anatomy-format.md) | Anatomy part declarations: field constraints, canonical vocabulary, SPEC-020/SPEC-023/SPEC-024/SPEC-025. |
62
+ | [State model](state-model.md) | State declarations: trigger semantics, precedence algorithm, canonical vocabulary, SPEC-022/SPEC-026. |
63
+ | [Cascade](cascade.md) | Layers, specificity, resolution algorithm. |
64
+ | [Dimensions](dimensions.md) | Dimension declarations, built-in dimensions, coverage. |
65
+ | [Manifest](manifest.md) | Platform manifest fields and validation expectations. |
66
+ | [Diff](diff.md) | Semantic diff change taxonomy, token identity, property changes. |
67
+ | [Query](query.md) | Filter notation for selecting tokens by structured fields. |
68
+ | [Evolution](evolution.md) | Deprecation lifecycle, migration windows, change classification. |
63
69
 
64
70
  ## JSON Schema `$id` and versioning
65
71
 
@@ -76,11 +82,11 @@ Packaged copies in this repository live under `packages/design-data-spec/schemas
76
82
 
77
83
  ## Relationship to existing Adobe packages
78
84
 
79
- | Package / area | Relationship |
80
- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
81
- | `@adobe/spectrum-tokens` | **Current** token JSON under `packages/tokens/` is the **legacy** shape (e.g. `color-set`, `scale-set`). This spec defines the **target** format; backward-compat schemas and migration are Phase 1 ([#723](https://github.com/adobe/spectrum-design-data/issues/723)). |
82
- | `@adobe/design-system-registry` | Registry enums and component metadata MAY be referenced by validation rules (e.g. component association); exact coupling is Layer 2. |
83
- | `@adobe/spectrum-component-api-schemas` | Component schemas MAY be cross-checked by graph rules; not required for Layer 1 structural validation. |
85
+ | Package / area | Relationship |
86
+ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | `@adobe/spectrum-tokens` | **Current** token JSON under `packages/tokens/` is the **legacy** shape (e.g. `color-set`, `scale-set`). This spec defines the **target** format; backward-compat schemas and migration are Phase 1 ([#723](https://github.com/adobe/spectrum-design-data/issues/723)). |
88
+ | `@adobe/design-system-registry` | Registry enums and component metadata MAY be referenced by validation rules (e.g. component association); exact coupling is Layer 2. |
89
+ | `@adobe/spectrum-component-api-schemas` | Will become a thin adapter over component declarations in `packages/design-data-spec/components/` (Phase 6.5). Until migration, existing schemas remain authoritative. Cross-reference rules SPEC-018–SPEC-020 apply when component declarations are present in the dataset. |
84
90
 
85
91
  ## Umbrella discussions
86
92
 
@@ -0,0 +1,245 @@
1
+ # State model
2
+
3
+ **Spec version:** `1.0.0-draft` (see [Overview](index.md))
4
+
5
+ This document defines the normative **state declaration** object: the named conditions that affect a component's visual appearance or token resolution, declared in the `states` array of a component declaration. State declarations complete the machine-readable contract introduced by the component declaration (see [Component format — States stub](component-format.md#states-stub)) and enable cross-reference validation between tokens and component surfaces.
6
+
7
+ Scoped under [RFC-A — Component Contract in Design Data Spec](https://github.com/adobe/spectrum-design-data/discussions/832). See also [Component format](component-format.md).
8
+
9
+ ## Introduction
10
+
11
+ A **component state** is a named condition under which a component's visual presentation differs from its baseline. States drive token resolution: when a component is in a given state, the design system selects tokens scoped to that state name rather than (or layered on top of) the baseline tokens.
12
+
13
+ State names appear in the `state` field of token name objects (see [Token format — Name object](token-format.md#name-object)). A token with `"state": "hover"` applies only when the component is in the `hover` state. For this cross-reference to be machine-enforceable, state declarations **MUST** be present on the component declaration.
14
+
15
+ States fall into two trigger types:
16
+
17
+ * **`prop`** — set by a persistent component API property (e.g. `isDisabled`, `isSelected`). The state remains active until the property value changes.
18
+ * **`interaction`** — set by runtime user interaction (hover, focus, pressed, dragging). The state is transient and is cleared when the interaction ends.
19
+
20
+ Full normative rules for the `states` array within a component declaration are in this document. The component declaration format is defined in [`spec/component-format.md`](component-format.md).
21
+
22
+ ## State declaration object
23
+
24
+ A state declaration is a **JSON object** that appears as an element of a component declaration's `states` array. Each state declaration object **MUST** validate against the standalone schema [`state-declaration.schema.json`](../schemas/state-declaration.schema.json) (canonical `$id`: `https://opensource.adobe.com/spectrum-design-data/schemas/v0/state-declaration.schema.json`).
25
+
26
+ **NORMATIVE:** `states` **MUST** be a JSON array within the component declaration. Each element **MUST** be a state declaration object.
27
+
28
+ ### Fields
29
+
30
+ | Field | Type | Required | Description |
31
+ | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
32
+ | `name` | string | REQUIRED | Kebab-case state identifier. **MUST** match the pattern `^[a-z][a-z0-9-]*$`. Used as the value of the `state` field in token name objects. |
33
+ | `description` | string | OPTIONAL | Plain-text description of the state's semantics and the conditions under which it is active. |
34
+ | `trigger` | string | OPTIONAL | `"prop"` for persistent prop-driven states; `"interaction"` for runtime interaction states. See [Trigger semantics](#trigger-semantics). |
35
+ | `precedence` | integer | OPTIONAL | Resolution precedence; higher value wins when multiple non-layered states are active simultaneously. Defaults to `0` if omitted. |
36
+ | `layered` | boolean | OPTIONAL | When `true`, this state composes on top of the winning non-layered state rather than competing with it. Default: `false`. |
37
+
38
+ **NORMATIVE:** No properties beyond those listed above are permitted in a state declaration object. Additional fields **MUST** cause a Layer 1 schema error.
39
+
40
+ ### `name`
41
+
42
+ **NORMATIVE:** `name` **MUST** match the pattern `^[a-z][a-z0-9-]*$` — lower-case kebab-case, non-empty.
43
+
44
+ **NORMATIVE:** `name` **MUST** be unique within the `states` array of a single component declaration. No two state declaration objects in the same component may share a `name` value.
45
+
46
+ **NORMATIVE:** Token name-object `state` field values referencing a component **MUST** match the `name` of a declared state on that component, when state declarations are present (rule SPEC-022). An undeclared `state` value is a validation error.
47
+
48
+ ### `description`
49
+
50
+ **OPTIONAL.** A plain-text description of the state's semantics and the conditions that activate it (e.g. `"Applied while the pointer is positioned over the component."`).
51
+
52
+ **RECOMMENDED:** Custom state names (those outside the [canonical state vocabulary](#canonical-state-vocabulary)) **SHOULD** include a `description` to document intent (rule SPEC-026 fires a warning for undocumented custom names).
53
+
54
+ ### `trigger`
55
+
56
+ **OPTIONAL.** One of two string values:
57
+
58
+ * `"prop"` — the state is driven by a persistent component API property. The state is active as long as the property holds a truthy or specific value (e.g. `isDisabled: true`).
59
+ * `"interaction"` — the state is driven by transient user interaction. The state activates when the interaction begins and clears when it ends.
60
+
61
+ When `trigger` is omitted, the trigger type is unspecified. Validators **MAY** warn for state declarations without a `trigger` when the component has states listed in the canonical vocabulary with a known trigger type.
62
+
63
+ ### `precedence`
64
+
65
+ **OPTIONAL.** A non-negative integer indicating this state's weight in the resolution algorithm. When multiple non-layered states are simultaneously active, the state with the highest `precedence` integer wins for token selection.
66
+
67
+ When `precedence` is omitted, it is treated as `0`.
68
+
69
+ **RECOMMENDED:** Components **SHOULD** declare explicit `precedence` values when two or more states could be active simultaneously, to avoid relying on declaration-order tie-breaking.
70
+
71
+ ### `layered`
72
+
73
+ **OPTIONAL.** A boolean indicating whether this state composes on top of the winning non-layered state instead of competing with it. Default: `false`.
74
+
75
+ When `layered: true`, the state does not participate in the non-layered precedence competition. Instead, after the winning non-layered state is determined, all active `layered: true` states are applied on top of it in order of their `precedence` values (higher precedence layered states apply last / outermost).
76
+
77
+ Typical use: focus ring states (`focus`, `focus-visible`) that must be visible regardless of whether the component is also in a `hover` or `selected` state.
78
+
79
+ ## Trigger semantics
80
+
81
+ ### `prop` trigger
82
+
83
+ A `prop` state is driven by a component API property. The state is persistent: it is active for the full duration that the property holds its activating value and is cleared only when the property changes.
84
+
85
+ Examples:
86
+
87
+ * `isDisabled: true` activates the `disabled` state.
88
+ * `isSelected: true` activates the `selected` state.
89
+ * `isIndeterminate: true` activates the `indeterminate` state.
90
+ * `isReadOnly: true` activates the `read-only` state.
91
+
92
+ `prop` states are typically mutually exclusive with each other in practice (a component is either disabled or selected, rarely both), but the spec permits multiple `prop` states to be simultaneously active; the precedence algorithm resolves which token set applies.
93
+
94
+ ### `interaction` trigger
95
+
96
+ An `interaction` state is driven by transient user input. The state is active only while the interaction is occurring and is automatically cleared when the interaction ends.
97
+
98
+ Examples:
99
+
100
+ * `hover` — active while a pointer device is positioned over the component's interactive area.
101
+ * `focus` — active while the component holds keyboard focus.
102
+ * `focus-visible` — active when focus was received via keyboard navigation (not pointer).
103
+ * `active` / `pressed` — active while a pointer button or key is held down.
104
+ * `dragging` — active while a drag-and-drop gesture originating from the component is in progress.
105
+
106
+ `interaction` states may overlay `prop` states (e.g. a disabled button may still receive a hover event on some platforms). When both are active, the precedence algorithm determines which token set wins, or, if the interaction state is `layered: true`, the interaction state's tokens are applied on top.
107
+
108
+ ## Precedence and resolution algorithm
109
+
110
+ The following algorithm is **NORMATIVE** for conforming validators and renderers that implement state-aware token resolution.
111
+
112
+ **Inputs:** A set of currently active state names for a given component instance.
113
+
114
+ **Algorithm:**
115
+
116
+ 1. Partition the active states into two groups:
117
+ * **Non-layered states:** states with `layered: false` (or `layered` omitted).
118
+ * **Layered states:** states with `layered: true`.
119
+
120
+ 2. Among non-layered states, select the **winning state**:
121
+ * Compare `precedence` values. The state with the highest `precedence` integer wins.
122
+ * If `precedence` is omitted, treat it as `0`.
123
+ * If two non-layered states tie on `precedence`, the state that appears **earlier** in the component's `states` array wins. **MUST** be treated as a tie; implementations **SHOULD** emit a warning (see SPEC-006 for the analogous cascade tie rule).
124
+
125
+ 3. Resolve tokens for the winning non-layered state. If no non-layered state is active, resolve the `default` state (or baseline tokens when no `default` state is declared).
126
+
127
+ 4. For each active layered state, in ascending `precedence` order (lowest first, so the highest-precedence layered state is applied last and sits outermost):
128
+ * Apply the layered state's tokens on top of the tokens resolved in step 3. Tokens explicitly declared for the layered state override the corresponding tokens from the non-layered resolution.
129
+
130
+ 5. The resulting merged token set is the resolved appearance for the component instance in its current state combination.
131
+
132
+ **Example:** A checkbox is simultaneously `selected` (precedence 80) and `hover` (precedence 50). `focus` (precedence 60, layered) is also active.
133
+
134
+ * Non-layered active states: `selected` (80), `hover` (50). Winner: `selected`.
135
+ * Layered active state: `focus`. Applied on top of `selected`.
136
+ * Resolution: `selected` tokens, with `focus` tokens composited over them.
137
+
138
+ ## Canonical state vocabulary
139
+
140
+ The following state names are defined by the cross-platform design audit and **SHOULD** be used in preference to custom names when their semantics match. Using canonical names enables cross-component tooling, documentation generation, and token audits.
141
+
142
+ | Name | Trigger | Precedence | Layered | Semantics |
143
+ | --------------- | ----------- | ---------- | ------- | ------------------------------------------------------------------------- |
144
+ | `default` | — | 0 | false | No special state; baseline component appearance. |
145
+ | `hover` | interaction | 50 | false | Pointer device is positioned over the component's interactive area. |
146
+ | `dragging` | interaction | 55 | false | A drag-and-drop gesture originating from this component is in progress. |
147
+ | `focus` | interaction | 60 | true | Component holds keyboard or programmatic focus. |
148
+ | `focus-visible` | interaction | 65 | true | Focus received via keyboard navigation; used for visible focus ring only. |
149
+ | `active` | interaction | 70 | false | Pointer button or activation key is currently held down. |
150
+ | `pressed` | interaction | 70 | false | Alias for `active`; prefer `active` for new declarations. |
151
+ | `invalid` | prop | 75 | false | Component value has failed validation. |
152
+ | `valid` | prop | 75 | false | Component value has passed validation. |
153
+ | `selected` | prop | 80 | false | Component is in a selected state (checkbox checked, tab active, etc.). |
154
+ | `indeterminate` | prop | 85 | false | Component has partial selection (tri-state checkbox mixed state). |
155
+ | `read-only` | prop | 90 | false | Component value is visible but not editable by the user. |
156
+ | `disabled` | prop | 100 | false | Component is non-interactive; all user input is suppressed. |
157
+
158
+ Custom state names are permitted. When a custom name is used, the state declaration object **SHOULD** include a `description` field explaining its semantics (rule SPEC-026).
159
+
160
+ **NORMATIVE:** The values in the canonical vocabulary table (trigger type, precedence, layered) are **RECOMMENDED defaults**. A component declaration MAY override any of these values for a canonical state name by explicitly declaring a different value in the state declaration object. When overriding a canonical default, the `description` **SHOULD** explain the deviation.
161
+
162
+ ## Cross-reference with token name objects
163
+
164
+ Token name objects use a `state` field to scope a token to a specific component state. The `state` field value must correspond to a state declared in the component's `states` array.
165
+
166
+ **NORMATIVE:** A token name-object `state` field value **MUST** match the `name` of a declared state on the component identified by the token's `component` field, when state declarations are present on that component (rule SPEC-022). A `state` value that does not match any declared state name is a validation error.
167
+
168
+ See [Token format — Name object](token-format.md#name-object) for the full name object field catalog.
169
+
170
+ **NORMATIVE:** The `state` field in a token name object **MUST NOT** be present unless the token's `component` field is also present. States are always scoped to a component.
171
+
172
+ ```json
173
+ {
174
+ "name": {
175
+ "component": "checkbox",
176
+ "anatomy": "checkmark",
177
+ "property": "color",
178
+ "state": "selected"
179
+ },
180
+ "value": "#0265dc"
181
+ }
182
+ ```
183
+
184
+ ## SPEC rules
185
+
186
+ The following rules in the Layer 2 rule catalog (`rules/rules.yaml`) apply to state declarations. SPEC-022 was introduced in Phase 6.1 (component-format); SPEC-026 is introduced by this chapter.
187
+
188
+ | Rule ID | Name | Severity | Assert |
189
+ | -------- | ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
190
+ | SPEC-022 | `component-state-valid` | error | Token `state` field value **MUST** match the `name` of a declared state on the referenced component (when state declarations are present). |
191
+ | SPEC-026 | `state-custom-name-documented` | warning | State declarations with a `name` outside the canonical state vocabulary **SHOULD** include a `description` field documenting the state's semantics. |
192
+
193
+ ## Full example
194
+
195
+ A complete `states` array for a checkbox component, demonstrating prop states (`selected`, `indeterminate`, `disabled`), interaction states (`hover`, `focus` with `layered: true`, `pressed`), and explicit precedence values:
196
+
197
+ ```json
198
+ "states": [
199
+ {
200
+ "name": "default",
201
+ "description": "Baseline unchecked checkbox appearance.",
202
+ "trigger": "prop",
203
+ "precedence": 0
204
+ },
205
+ {
206
+ "name": "hover",
207
+ "description": "Applied while a pointer device is positioned over the checkbox.",
208
+ "trigger": "interaction",
209
+ "precedence": 50
210
+ },
211
+ {
212
+ "name": "focus",
213
+ "description": "Applied while the checkbox holds keyboard or programmatic focus. Composes on top of the active non-layered state.",
214
+ "trigger": "interaction",
215
+ "precedence": 60,
216
+ "layered": true
217
+ },
218
+ {
219
+ "name": "pressed",
220
+ "description": "Applied while the pointer button or Space key is held down during activation.",
221
+ "trigger": "interaction",
222
+ "precedence": 70
223
+ },
224
+ {
225
+ "name": "selected",
226
+ "description": "Applied when isSelected is true (checkbox checked).",
227
+ "trigger": "prop",
228
+ "precedence": 80
229
+ },
230
+ {
231
+ "name": "indeterminate",
232
+ "description": "Applied when isIndeterminate is true (tri-state mixed selection).",
233
+ "trigger": "prop",
234
+ "precedence": 85
235
+ },
236
+ {
237
+ "name": "disabled",
238
+ "description": "Applied when isDisabled is true. Suppresses all user input.",
239
+ "trigger": "prop",
240
+ "precedence": 100
241
+ }
242
+ ]
243
+ ```
244
+
245
+ In this example, if the checkbox is simultaneously `selected` (80) and `hover` (50) with `focus` (60, layered) active, the resolution is: `selected` wins the non-layered competition; `focus` tokens are then applied on top of the `selected` tokens.
@@ -12,13 +12,33 @@ A **token** is a JSON object that satisfies the Layer 1 schema [`token.schema.js
12
12
 
13
13
  A token **MUST** contain:
14
14
 
15
- 1. **`name`** — a JSON object (the **name object**) as defined below.
15
+ 1. **`name`** — a JSON object (the **name object**) as defined below, or a non-empty plain string (escape hatch — see [String-name escape hatch](#string-name-escape-hatch)).
16
16
  2. Exactly one of:
17
17
  * **`value`** — a literal token value (type depends on value-type schema), or
18
18
  * **`$ref`** — a string reference to another token (alias).
19
19
 
20
20
  A token **MUST NOT** include both `value` and `$ref`.
21
21
 
22
+ ### String-name escape hatch
23
+
24
+ A token's `name` field **MAY** be a non-empty plain string instead of a name object when the token's identity cannot be expressed using the structured taxonomy fields.
25
+
26
+ ```json
27
+ {
28
+ "name": "focus-ring-color-key-focus",
29
+ "value": "#0265dc",
30
+ "uuid": "aaaaaaaa-0011-4000-8000-000000000001"
31
+ }
32
+ ```
33
+
34
+ **NORMATIVE:** String-named tokens are schema-valid. Validators **MUST** accept them without a structural error.
35
+
36
+ **NORMATIVE:** String-named tokens **MUST** trigger rule SPEC-017 (severity: `warning`, category: `tech-debt`). The warning surfaces the token as tracked debt requiring future remediation.
37
+
38
+ **NORMATIVE:** String-named tokens **MUST NOT** participate in name-object cascade dimension matching, specificity calculation, or registry vocabulary checks (SPEC-009 does not apply).
39
+
40
+ **RECOMMENDED:** Authors **SHOULD** treat string names as a temporary escape hatch and track a remediation plan. Each string-named token **SHOULD** eventually be given a structured name object, at which point SPEC-017 no longer fires.
41
+
22
42
  ### Name object
23
43
 
24
44
  The **name object** identifies the token in a structured way. Implementations use it for cascade matching, specificity, and tooling.
@@ -72,16 +92,15 @@ When **`value`** is present, it **MUST** conform to the declared value type for
72
92
 
73
93
  The following OPTIONAL fields implement the token lifecycle model described in [#623](https://github.com/adobe/spectrum-design-data/discussions/623) and the evolution policy in [Evolution](evolution.md). Inspired by Swift's `@available` attribute, Kotlin's `@Deprecated`, and OpenAPI 3.3's deprecation model.
74
94
 
75
- | Field | Type | Description |
76
- | -------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
77
- | `uuid` | string (UUID) | Stable unique id for rename tracking and diffs. |
78
- | `introduced` | string (version) | Spec version when the token was first added (e.g. `"1.0.0"`). |
79
- | `lastModified` | string (version) | Spec version when the token's value or metadata last changed (e.g. `"2.1.0"`). Updated on any non-formatting change. |
80
- | `deprecated` | string (version) | Spec version when the token was deprecated (e.g. `"3.2.0"`). Truthy = deprecated. |
81
- | `deprecated_comment` | string | Human-readable deprecation explanation and migration guidance. |
82
- | `replaced_by` | string (UUID) or array of string (UUID) | UUID(s) of the replacement token(s). Single string for 1:1 replacement; array for one-to-many splits. |
83
- | `plannedRemoval` | string (version) | Spec version when the token will be removed. If omitted, defaults to the next major version after `deprecated`. |
84
- | `private` | boolean | Not part of public API surface. |
95
+ | Field | Type | Description |
96
+ | -------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
97
+ | `uuid` | string (UUID) | Stable unique id for rename tracking and diffs. |
98
+ | `introduced` | string (version) | Spec version when the token was first added (e.g. `"1.0.0"`). |
99
+ | `deprecated` | string (version) | Spec version when the token was deprecated (e.g. `"3.2.0"`). Truthy = deprecated. |
100
+ | `deprecated_comment` | string | Human-readable deprecation explanation and migration guidance. |
101
+ | `replaced_by` | string (UUID) or array of string (UUID) | UUID(s) of the replacement token(s). Single string for 1:1 replacement; array for one-to-many splits. |
102
+ | `plannedRemoval` | string (version) | Spec version when the token will be removed. If omitted, defaults to the next major version after `deprecated`. |
103
+ | `private` | boolean | Not part of public API surface. |
85
104
 
86
105
  #### Lifecycle example
87
106
 
@@ -91,7 +110,6 @@ The following OPTIONAL fields implement the token lifecycle model described in [
91
110
  "value": "#0265dc",
92
111
  "uuid": "aaaaaaaa-0001-4000-8000-000000000001",
93
112
  "introduced": "1.0.0",
94
- "lastModified": "3.2.0",
95
113
  "deprecated": "3.2.0",
96
114
  "deprecated_comment": "Use action-background-default instead.",
97
115
  "replaced_by": "bbbbbbbb-0002-4000-8000-000000000001",
@@ -111,15 +129,13 @@ The following OPTIONAL fields implement the token lifecycle model described in [
111
129
 
112
130
  **NORMATIVE:** Each UUID in `replaced_by` **MUST** resolve to an existing token in the dataset (see rule `SPEC-010`).
113
131
 
114
- **NORMATIVE:** If `lastModified` is present, its version **MUST NOT** precede `introduced` (see rule `SPEC-014`). Authors **SHOULD** bump `lastModified` whenever a token's `value`, alias `$ref`, or non-formatting metadata changes; pure formatting or comment-only edits do not require a bump.
115
-
116
132
  #### Legacy format mapping
117
133
 
118
134
  When generating legacy-format output from cascade tokens:
119
135
 
120
136
  * `deprecated: "3.2.0"` maps to `deprecated: true`
121
137
  * `replaced_by: "<uuid>"` maps to `renamed: "<target-token-name>"` (resolved via UUID lookup)
122
- * `introduced`, `lastModified`, and `plannedRemoval` have no legacy equivalent and are not emitted
138
+ * `introduced` and `plannedRemoval` have no legacy equivalent and are not emitted
123
139
 
124
140
  When migrating legacy-format tokens to cascade:
125
141
 
@@ -153,6 +169,55 @@ Example:
153
169
 
154
170
  Individual types (color, dimension, opacity, etc.) **MUST** be defined as JSON Schemas under `schemas/value-types/` and **MUST** use `$id` under the same `v0` base path as [Overview — JSON Schema `$id`](index.md).
155
171
 
172
+ ### Value-type declaration (`$valueType`)
173
+
174
+ A token **MAY** include a `$valueType` field: a URI reference pointing to a value-type schema under `schemas/value-types/`.
175
+
176
+ ```json
177
+ {
178
+ "name": { "property": "typography-scale", "scale": "desktop" },
179
+ "$valueType": "value-types/typography-scale.schema.json",
180
+ "value": { "fontSize": "14px", "lineHeight": "18px" },
181
+ "uuid": "377145e8-079b-43fd-b522-8f16b1b8f883"
182
+ }
183
+ ```
184
+
185
+ **NORMATIVE:** When `$valueType` is present on a token with a literal `value`, the value **MUST** validate against the referenced schema (rule SPEC-016).
186
+
187
+ **NORMATIVE:** When `$valueType` is present on an alias token (`$ref`), the alias resolution chain **MUST** terminate at a token whose value validates against the same value-type schema (rule SPEC-002, extended).
188
+
189
+ **RECOMMENDED:** All tokens with composite values (see below) **SHOULD** include `$valueType` to enable tooling discovery and validation. Tokens without `$valueType` remain valid under the permissive `anyOf` union in `token.schema.json`.
190
+
191
+ ### Composite value types
192
+
193
+ A **composite value type** is a value-type schema whose root type is `object` or `array`. Composite values bundle multiple sub-values into a single token.
194
+
195
+ **NORMATIVE:** Composite value types **MUST** be defined as JSON Schemas under `schemas/value-types/` following the same conventions as primitive value types.
196
+
197
+ **NORMATIVE:** A composite token participates in cascade resolution as an **atomic unit**. Sub-values within a composite **MUST NOT** independently match, override, or participate in specificity calculation.
198
+
199
+ ### Inline alias references
200
+
201
+ Within a composite value, a string sub-value **MAY** be an **inline alias**: a reference to another token that is resolved to produce the final sub-value.
202
+
203
+ ```json
204
+ {
205
+ "name": { "property": "component-m-regular" },
206
+ "$valueType": "value-types/typography.schema.json",
207
+ "value": {
208
+ "fontFamily": "{sans-serif-font-family}",
209
+ "fontSize": "{font-size-100}",
210
+ "fontWeight": "{regular-font-weight}",
211
+ "letterSpacing": "{letter-spacing}",
212
+ "lineHeight": "{line-height-font-size-100}"
213
+ }
214
+ }
215
+ ```
216
+
217
+ **NORMATIVE:** In legacy format, inline aliases use the syntax `{token-name}` (curly-brace-wrapped token property name). In cascade format, the inline alias syntax is `{token-name}` for backward compatibility; UUID-based inline aliases are reserved for a future spec version.
218
+
219
+ **NORMATIVE:** Inline aliases within composite values are subject to alias resolution rules. Validators **MUST** resolve inline aliases and report errors for missing targets (SPEC-014), type mismatches (SPEC-015), and circular references (SPEC-003, extended).
220
+
156
221
  ## Relationship to legacy Spectrum tokens
157
222
 
158
223
  The current `@adobe/spectrum-tokens` JSON uses **sets** (`color-set`, `scale-set`, …). This specification describes the **target** per-token model. Mapping from legacy to this format is out of scope for this document; see [#743](https://github.com/adobe/spectrum-design-data/issues/743).
@@ -0,0 +1,61 @@
1
+ /*
2
+ Copyright 2026 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ // Canonical vocabulary sets derived from spec chapters.
14
+ // Sources: spec/component-format.md, spec/anatomy-format.md, spec/state-model.md
15
+
16
+ export const CANONICAL_SLOTS = new Set([
17
+ "default",
18
+ "icon",
19
+ "label",
20
+ "help-text",
21
+ "negative-help-text",
22
+ "action",
23
+ "heading",
24
+ "description",
25
+ "hero",
26
+ "footer",
27
+ "tooltip",
28
+ ]);
29
+
30
+ export const CANONICAL_ANATOMY_PARTS = new Set([
31
+ "body",
32
+ "checkmark",
33
+ "disclosure-triangle",
34
+ "field",
35
+ "handle",
36
+ "header",
37
+ "icon",
38
+ "label",
39
+ "picker",
40
+ "progress-bar",
41
+ "swatch",
42
+ "thumbnail",
43
+ "track",
44
+ "value",
45
+ ]);
46
+
47
+ export const CANONICAL_STATES = new Set([
48
+ "default",
49
+ "hover",
50
+ "focus",
51
+ "focus-visible",
52
+ "active",
53
+ "pressed",
54
+ "selected",
55
+ "indeterminate",
56
+ "disabled",
57
+ "read-only",
58
+ "invalid",
59
+ "valid",
60
+ "dragging",
61
+ ]);