You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Coordination:docs/rfc-coordination.md — RFC family table (landed in PR #826). All open questions from this RFC have been resolved.
1. Summary
Absorb component options, anatomy, and state model into design-data-spec, with cross-reference SPEC rules that catch mismatches between token name-object fields and component declarations. Migrate @adobe/spectrum-component-api-schemas (v6) to a thin adapter that preserves its existing index.js API, so consumers (today: the s2 docs site) are unaffected.
After this RFC, design-data-spec is the single source of truth for tokens, component options, anatomy, and state. The package name should match the scope: it is "Design Data," not "Design Tokens."
2. Problem statement
The spec today has three structural gaps that compound when AI agents or humans generate UI from token data:
2.1 Component contract lives in a disjoint package
@adobe/spectrum-component-api-schemas covers component options well — per-component allowed enums (variant ∈ {accent, negative, primary, secondary}), defaults, $ref to typed primitives, AJV-driven validation. But it is disjoint from the token taxonomy. There is no rule today that detects:
A token with component=button variant=foo when foo isn't a real button variant.
A token with component=checkbox variant=accent when checkbox doesn't accept that variant.
A token with anatomy=handle on a component that has no handle.
The disjointness is the bug. Cross-validation can only happen if the two systems share a contract.
2.2 Anatomy is implicit; cross-field rules live in prose
Anatomy is a token name-object field (anatomy=icon, anatomy=label), but anatomy parts are not declared anywhere. The set of valid anatomy values for component=button is whatever tokens happen to use today. Cross-field constraints live in description prose only.
2.3 The state field conflates two different concepts
Persistent props (isDisabled, isSelected, isReadOnly, isInvalid) compose — a checkbox can be both disabled and selected. Interactive states (hover, focus, pressed) compete — they're triggered by runtime interaction, not by props. The single state field can't express both:
A flat enum can't say "disabled wins over selected."
A flat enum can't say "this state comes from a prop, that one from interaction."
A flat enum can't say "focus stacks on hover via outline rather than competing for the same token slot."
3. Proposed solution
design-data-spec gains three new chapters and a new package boundary.
3.1 Component format chapter (spec/component-format.md)
required is a boolean on the part entry. Parts that must be present in every token that references this component set "required": true.
contains is informative, not prescriptive, and is omitted from generated files. It would describe conceptual nesting that matches the Figma asset; implementations may flatten or invert this hierarchy as needed.
JSON Schema combinators (anyOf, oneOf) are not required at the anatomy level. Cross-field constraints that are too complex for the flat array form can be expressed in prose description fields or deferred to future spec evolution.
3.3 State precedence + triggers (spec/state-model.md)
Per-component state declarations are an array with explicit triggers and integer precedence:
Highest-precedence active state wins. A disabled+selected checkbox resolves to the state=disabled token (precedence 100 > 60), matching how design systems actually render multi-state combinations.
Triggers separate persistent from interactive."trigger": "prop" means a boolean prop on the component drives this state; "trigger": "interaction" means the runtime detected a user interaction. The prop name is not encoded — one prop does not always map 1:1 to one state across all components.
layered: true composes rather than competes. Focus typically renders as a separate decoration token (outline) layered on top of hover, not as a replacement.
Two new SPEC rules fall out:
Every state value referenced by tokens for a component must be declared on that component.
Precedence values must be totally ordered (no ties) within a component.
3.4 Cross-reference SPEC rules
Rule
Slug
Level
Description
SPEC-018
component-name-exists
error
A token's component field MUST reference the name of a declared component.
SPEC-019
component-variant-valid
error
A token's variant field value MUST match a value in the declared variant option enum for the referenced component (when that enum exists).
SPEC-020
component-anatomy-valid
error
A token's anatomy field value MUST match the name of a declared anatomy part on the referenced component.
SPEC-021
component-slot-vocabulary
warning
Component slots entries with a name outside the canonical vocabulary SHOULD include a description. Custom slot names without descriptions are surfaced as warnings.
SPEC-022
component-state-valid
error
A token's state field value MUST match the name of a declared state on the referenced component (when state declarations are present).
Each rule lands with conformance fixtures (conformance/valid/, conformance/invalid/SPEC-0XX-*/) and is enforced by the existing Rust validator in sdk/.
3.5 Adapter migration of @adobe/spectrum-component-api-schemas
Source schemas move to packages/design-data-spec/components/. The existing @adobe/spectrum-component-api-schemas package becomes a thin adapter:
Adapter: packages/component-schemas/index.js retargets its glob to the new location and continues exposing getAllSchemas, getSchemaBySlug, getAllSlugs, getSchemaFile with identical signatures and return shapes.
meta.documentationUrl is preserved (slug derivation depends on it).
The s2 docs site — sole consumer of the helper API — renders component options tables identically without code changes.
Backward-compat aliases: returned schema objects include title (= displayName) and properties (= options) alongside the new fields.
Pattern mirrors the existing legacy-spectrum-tokens vs design-data-spec adapter shape.
The Phase 6.0 audit found insufficient cross-platform consistency in event callback signatures to include events in v1. Events are deferred to a follow-up RFC where the divergence between web, iOS, and Android can be addressed without holding back the rest of Phase 6. Slots (content projection points) are included as a first-class block in the component format.
Patterns
Patterns (multi-component flows, interaction sequences) are inherently cross-component, prose-heavy, and weakly machine-readable. Out of scope here.
Component documentation prose
Document blocks (purpose, guideline, do-don't, examples, accessibility prose) are scoped under the planned Phase 9 / RFC-D and are parallelizable with Phase 7.
6. Open questions
Slots and events scope — Resolved. Slots field included in component format; events deferred to future RFC based on Phase 6.0 audit (PR #849).
Boolean-prop ↔ state mapping convention — Resolved.trigger: "prop" — no prop name encoded in the trigger field. The "prop:isX" form was rejected because real components have boolean props that don't map 1:1 to states.
Anatomy contains semantics — Resolved. Informative-only; not required in generated component files. Can be added to individual files as needed.
State precedence as integers vs. named tiers — Resolved. Integers as proposed — the only approach that scales to arbitrary state compositions.
Component-level deprecation cascade — Open. If a component is deprecated, do its dependent tokens auto-deprecate? Resolution tracked in #735; referenced here as a cross-cutting concern.
133 S2 anatomy term audit (from #806) — Resolved. Folded into Phase 6.2 (Anatomy declarations, PR #851).
Have a question about the implemented format? Comment on this discussion or open an issue.
Concerned about the adapter migration and your consumer? If you use @adobe/spectrum-component-api-schemas directly beyond the getAllSchemas / getSchemaBySlug helpers, please flag it on this thread.
Want to track the remaining open question (deprecation cascade)? Watch #735.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
[RFC-A] Component Contract in Design Data Spec
1. Summary
Absorb component options, anatomy, and state model into design-data-spec, with cross-reference SPEC rules that catch mismatches between token name-object fields and component declarations. Migrate
@adobe/spectrum-component-api-schemas(v6) to a thin adapter that preserves its existingindex.jsAPI, so consumers (today: the s2 docs site) are unaffected.After this RFC, design-data-spec is the single source of truth for tokens, component options, anatomy, and state. The package name should match the scope: it is "Design Data," not "Design Tokens."
2. Problem statement
The spec today has three structural gaps that compound when AI agents or humans generate UI from token data:
2.1 Component contract lives in a disjoint package
@adobe/spectrum-component-api-schemascovers component options well — per-component allowed enums (variant ∈ {accent, negative, primary, secondary}), defaults,$refto typed primitives, AJV-driven validation. But it is disjoint from the token taxonomy. There is no rule today that detects:component=button variant=foowhenfooisn't a real button variant.component=checkbox variant=accentwhen checkbox doesn't accept that variant.anatomy=handleon a component that has nohandle.The disjointness is the bug. Cross-validation can only happen if the two systems share a contract.
2.2 Anatomy is implicit; cross-field rules live in prose
Anatomy is a token name-object field (
anatomy=icon,anatomy=label), but anatomy parts are not declared anywhere. The set of valid anatomy values forcomponent=buttonis whatever tokens happen to use today. Cross-field constraints live in description prose only.2.3 The
statefield conflates two different conceptsPersistent props (
isDisabled,isSelected,isReadOnly,isInvalid) compose — a checkbox can be both disabled and selected. Interactive states (hover, focus, pressed) compete — they're triggered by runtime interaction, not by props. The singlestatefield can't express both:3. Proposed solution
design-data-spec gains three new chapters and a new package boundary.
3.1 Component format chapter (
spec/component-format.md)A
componententity declares:{ "$schema": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/component.schema.json", "$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/components/button.json", "specVersion": "1.0.0-draft", "name": "button", "displayName": "Button", "meta": { "category": "actions", "documentationUrl": "https://spectrum.adobe.com/page/button/" }, "options": { "variant": { "type": "string", "enum": ["accent", "negative", "primary", "secondary"], "default": "accent" }, "size": { "type": "string", "enum": ["s", "m", "l", "xl"], "default": "m" }, "isDisabled": { "type": "boolean", "default": false }, "isPending": { "type": "boolean", "default": false } }, "anatomy": [ /* see §3.2 */ ], "states": [ /* see §3.3 */ ], "lifecycle": { "introduced": "1.0.0-draft" } }optionsmirrors the current@adobe/spectrum-component-api-schemasJSON Schema option declarations almost exactly —type,enum,default,description,$refto typed primitives. Existing schemas migrate without semantic change.3.2 Anatomy declarations (
spec/anatomy-format.md)Anatomy parts are first-class declarations on the component as an array, not just emergent values from token name-objects:
Design choices:
requiredis a boolean on the part entry. Parts that must be present in every token that references this component set"required": true.containsis informative, not prescriptive, and is omitted from generated files. It would describe conceptual nesting that matches the Figma asset; implementations may flatten or invert this hierarchy as needed.anyOf,oneOf) are not required at the anatomy level. Cross-field constraints that are too complex for the flat array form can be expressed in prosedescriptionfields or deferred to future spec evolution.3.3 State precedence + triggers (
spec/state-model.md)Per-component state declarations are an array with explicit triggers and integer precedence:
Resolver semantics:
state=disabledtoken (precedence 100 > 60), matching how design systems actually render multi-state combinations."trigger": "prop"means a boolean prop on the component drives this state;"trigger": "interaction"means the runtime detected a user interaction. The prop name is not encoded — one prop does not always map 1:1 to one state across all components.layered: truecomposes rather than competes. Focus typically renders as a separate decoration token (outline) layered on top of hover, not as a replacement.Two new SPEC rules fall out:
statevalue referenced by tokens for a component must be declared on that component.3.4 Cross-reference SPEC rules
component-name-existscomponentfield MUST reference thenameof a declared component.component-variant-validvariantfield value MUST match a value in the declaredvariantoption enum for the referenced component (when that enum exists).component-anatomy-validanatomyfield value MUST match thenameof a declared anatomy part on the referenced component.component-slot-vocabularyslotsentries with anameoutside the canonical vocabulary SHOULD include adescription. Custom slot names without descriptions are surfaced as warnings.component-state-validstatefield value MUST match thenameof a declared state on the referenced component (when state declarations are present).Each rule lands with conformance fixtures (
conformance/valid/,conformance/invalid/SPEC-0XX-*/) and is enforced by the existing Rust validator insdk/.3.5 Adapter migration of
@adobe/spectrum-component-api-schemasSource schemas move to
packages/design-data-spec/components/. The existing@adobe/spectrum-component-api-schemaspackage becomes a thin adapter:packages/design-data-spec/components/*.json(now declaring anatomy + states + lifecycle alongside existing options).packages/component-schemas/index.jsretargets itsglobto the new location and continues exposinggetAllSchemas,getSchemaBySlug,getAllSlugs,getSchemaFilewith identical signatures and return shapes.meta.documentationUrlis preserved (slug derivation depends on it).title(=displayName) andproperties(=options) alongside the new fields.4. Phased plan
Tracked in Epic #828 — Phase 6: Component Contract on project 89.
@adobe/spectrum-component-api-schemas5. Out of scope (for v1)
Events — deferred after audit (#827)
The Phase 6.0 audit found insufficient cross-platform consistency in event callback signatures to include events in v1. Events are deferred to a follow-up RFC where the divergence between web, iOS, and Android can be addressed without holding back the rest of Phase 6. Slots (content projection points) are included as a first-class block in the component format.
Patterns
Patterns (multi-component flows, interaction sequences) are inherently cross-component, prose-heavy, and weakly machine-readable. Out of scope here.
Component documentation prose
Document blocks (purpose, guideline, do-don't, examples, accessibility prose) are scoped under the planned Phase 9 / RFC-D and are parallelizable with Phase 7.
6. Open questions
trigger: "prop"— no prop name encoded in the trigger field. The"prop:isX"form was rejected because real components have boolean props that don't map 1:1 to states.containssemantics — Resolved. Informative-only; not required in generated component files. Can be added to individual files as needed.7. References
RFC family touch points
Tracking
Related work in the wider ecosystem
8. How to engage
@adobe/spectrum-component-api-schemasdirectly beyond thegetAllSchemas/getSchemaBySlughelpers, please flag it on this thread.Beta Was this translation helpful? Give feedback.
All reactions