Repository navigation
[DRAFT RFC] Separate token taxonomy from component-relative data #1453
Replies: 3 comments
|
I've created a draft PR for this discussion/RFC here: #1454 |
|
One thing worth making explicit here, since it's a fair angle to attack this proposal from A few things fall out of checking this against the schema and the live corpus directly, rather Most of the apparent overlap is one generic mechanism, not several ad hoc ones. A smaller set of fields have dedicated, purpose-built cross-reference rules instead of the But those four aren't all the same kind of thing, and I don't want to conflate them.
That distinction matters beyond just labeling, because of what CTR already is. I checked One more relationship in the diagram is worth calling out as a different phenomenon None of this changes what's actually on the table in this PR. It cleans up the fields whose
|
|
@GarthDB are there any issues you see with this proposal? |

Uh oh!
There was an error while loading. Please reload this page.
[DRAFT RFC] Separate universal token taxonomy from component-relative data
Scoped under: RFC umbrella #714
Continues: #806 — Token Taxonomy, Vocabulary, and Formatting, #832 — Component Contract in Design Data Spec (RFC-A)
Summary
Proposes removing
substructureandrolefrom the token name-object taxonomy, renamingobjecttoelement(narrowing it to generic cross-component surfaces), addingattribute/affordance/visibilityas new universal fields, and splittingstateintointeraction(transient) andinteraction-context(persistent) — so that the shared taxonomy (packages/design-data/fields/,registry/) only ever holds terms that are genuinely universal and cross-platform, never component-relative or platform-specific ones.The problem
Token taxonomy is a classification system for generic, reusable design concepts — things that mean the same thing no matter which component or platform they show up on. Component data is explicitly component-relative, and its terminology can vary by platform. These are two different vocabularies with two different owners, and the current spec lets them bleed into each other.
Concretely, today: a
componentfield exists on tokens but is used by only 5 of 2,374 tokens. 41 of 57 tokens usinganatomyhave nocomponent/structureanchor at all, and only pass validation because the registry has been reactively flaggedstandaloneScope: trueto match whatever was already there — not because anyone decided those parts were genuinely ownerless. Neither of those was a deliberate design; they accreted.variantis a subtler case, worth being precise about rather than folding into the same "it accreted" claim.registry/variants.jsonalready tagssubtle,subdued, andemphasizedwithcategory: "emphasis"— but so areaccent,primary,secondary,quiet, andhero, which are legitimate variants (distinct component-identity choices) and aren't part of this proposal's split. So there was prior awareness that "emphasis" is a distinct axis from color/semantic role — it just never became a schema-level field boundary, only an informal tag inside one shared registry. The specific split this RFC proposes (subtle→default→emphasizedas a continuous prominence scale, pulled into a newvisibilityfield, versusaccent/primary/secondary/quiet/herostaying invariantas discrete style choices) is a judgment call about which "emphasis"-tagged terms form a scale versus which are identities — the existingcategorytag doesn't resolve that on its own, and it's fair to ask whether the line was drawn in the right place.Precedent — we've already agreed to this principle twice
This isn't a new argument. It's asking the repo to hold a line it already drew, twice, in discussions that are both marked Implemented.
#806 proposed pulling
component,anatomy,variant, andstateinto token taxonomy as ordinary fields. The response at the time:And the resolution, months later:
#832 — self-titled "[RFC-A] Component Contract in Design Data Spec," part of this repo's own lettered RFC series (referred to as "RFC-A" throughout the rest of this document) — is where that cross-reference mechanism actually got built (SPEC-018–022), specifically to fix a disjointness problem: component options and token taxonomy were two unconnected systems, so nothing caught
component=button variant=foowhenfooisn't a real button variant.This RFC is not re-litigating either of those. It's the same principle, applied to fields #806/#832 didn't touch, plus a fix for places where implementation drifted from the boundary #806 already set.
Proposed changes
substructure(zero real usage today) androle(duplicative ofstructure/the proposedelement).object→element, narrowed to generic, cross-component surfaces only (text,visual,bar,control, plus the recognized outliersworkflow-icon/ui-icon). Property sub-qualities currently folded intoobject(background,border,corner,overlay, etc.) move to a new field instead of staying in this one.attribute— sub-qualities of a property, paired withproperty(border,background,dash,shadow,corner,overlay,gradient) instead of overloadingpropertywith compound terms likebackground-color.affordance— cross-component behavior indicators (drop-target,focus-ring,selection-indicator,drag-handle).visibility— prominence level (subtle,subdued,emphasized), pulled out ofvariantwhere it's currently miscategorized as a color/semantic variant when it's really a different axis entirely.stateintointeraction(transient) andinteraction-context(persistent) — see below.propertyvocabulary: removebackground-color,border-color,corner-radius,font-size(expressed instead asproperty+attributepairs, e.g.color+background); addlength,offset,angle,step,radius,aspect-ratio,stop.Why the split matters — with real examples, not hypotheticals
Property-level naming in this repo is, in practice, mostly platform-agnostic today — the real divergence risk is one level up, in variants and states. Component option keys like
cornerRadius(swatch-group),containerPadding(tooltip, popover, contextual-help),iconSize(close-button), andmaxWidth(tooltip) aren't platform-flavored in any meaningful sense:cornerRadiusis iOS-native terminology (CALayer.cornerRadius), not a web borrowing, and the rest are just camelCase spellings of generic concepts (padding, size, width) every platform has its own version of. Property naming isn't where the real risk is.Variants and states are where real divergence already shows up.
registry/variants.json'sconfirmation,destructive,warning,error, andinformationentries are taggedcategory: "semantic", same asnegative/positive/notice/informative/neutral— but their own descriptions self-identify as pattern-specific: "Confirmation dialog variant," "Warning dialog variant for cautionary alerts," "Informational dialog variant... alias for informative in alert contexts."errorin particular reads as a dialog/alert-pattern label wearing a "semantic variant" hat, not a universal role, despite sitting in the same shared registry as the terms that are.State-term divergence is shipped, not theoretical.
registry/platform-extensions/web-components-interactions.json— production data, not a fixture — already maps the foundation'sfocusinteraction term to Web Components' own"focused".taxonomy.mdnames this exact tension directly: "the vocabulary is shared with platform-aware compromises," givinghover→highlightedfor iOS as its own illustrative example (spec prose, not yet a shipped manifest, but normative intent, not speculation on this RFC's part).docs/proposals/006-vocabulary-gaps.mdis a related but separate point, worth keeping distinct from the above: it's about token property names in the legacy corpus (card-background-well-color,stack-item-selected-background-color-highlight) still awaiting decomposition intoproperty+attribute— a token-taxonomy cleanup, not a component-option naming question.taxonomy.mdalready namesbackground-coloras the anti-pattern theattributesplit is meant to fix.Cross-platform terminology divergence is also a documented finding from RFC-A (#832) itself, not a new claim. Its Phase 6.0 audit (
audits/events.audit.md) found "SWC uses DOM event strings (change,sp-opened), RSP uses camelCase callback props (onChange,onOpenChange), and iOS uses closure params with different names (action,@Binding)," concluding "the naming model is not cross-platform stable" — which is why events were deferred from the component contract entirely, by an accepted governance decision, not left unresolved by oversight. This is the same shape of risk as variants/states, one layer further out.How the architecture already handles platform divergence
None of this requires new machinery. The spec already has three independent, existing ways for a platform to diverge from the foundation's terminology, at three different layers:
manifest.md's platform-extensions mechanism lets a platform declare{"extends": "<registry>", "extensions": [{"termId": ..., "platformTerm": ...}]}— but only as an annotation of an existing foundation id, never a new one.registry/platform-extensions/web-components-interactions.jsonis production data doing exactly this today: the foundation'sfocusis relabeled"focused"for Web Components.extensions/components/lets a platform add a net-new component, or fully replace an existing one by name — andcomponent.schema.json's option-key shape (optionsMap) has no registry/enum constraint on option names at all, unlikevariantvalues, which are checked against the shared registry viaSPEC-040. A platform can already define a component with whatever option vocabulary it wants.SPEC-022validates a token'sstate(and would validateinteraction/interaction-contextunder this proposal) against that component's own declaredstates[]— not a shared registry — the same waySPEC-019already lets a component declare its ownvariantenum (e.g. Badge's"info"rather than the universal"informative").Worth being precise about what this doesn't touch: cascade specificity is scored purely over mode-set fields (
colorScheme,scale,contrast) percascade.md—component/structurearen't specially weighted, so divergent terminology isn't a specificity risk.What is true: all three mechanisms above only stay simple if the foundation vocabulary they build on is genuinely universal. Let component- or platform-relative terms into the shared registries directly, and mechanism (1) breaks down first — you can't cleanly relabel a "foundation" term that's already someone's local dialect. This RFC isn't proposing a fourth mechanism; it's protecting the assumption the existing three already depend on.
One concrete gap in mechanism (3), flagged rather than fixed here: a "universal" interaction registry only stays meaningfully universal if it's actively kept in sync as real cross-platform components show up. Today, if an Android component declared
states: [{"name": "pressed", "trigger": "interaction"}], a token usinginteraction: ["pressed"]would validate cleanly — nothing forces"pressed"to be spelled"down", and it would just silently trip an advisory "not in the registry" warning without anyone connecting the two. The registry-value schema already supports fixing this via analiasesfield (today'sstates.jsonuses it —"active"is declaredaliases: ["pressed"]), so the mechanism exists; it's just not populated for a platform that has no real components in the corpus yet. Not proposing that fix here — we have zero real non-web/iOS components today, and RFC-A (#832) already set the precedent for waiting on real data rather than guessing (it deferred events for the same reason). Revisit once a real second-platform component set exists to map against.What "foundational" vs. "platform-specific" means going forward
This RFC assumes a specific shape for where data lives, made explicit here rather than left implicit:
→ (1) and (2) are foundational — the core of this repository.
(4) and (5) are already structurally supported today — mechanisms (1)–(3) above. This RFC doesn't need to build that capability, only keep the foundation side of the line from being muddied by (4)/(5)-shaped data leaking upward into (1)/(2).
A concrete illustration of (4)/(5): a token classified in this taxonomy as
element: "text"+attribute: "color"is universal at the taxonomy level, but each platform's own component/rendering layer maps that same pair to a different idiomatic property name —colorin HTML/CSS,textColorin UIKit,foregroundColorin SwiftUI,textColorin Android XML/View, or thecolorparameter on Jetpack Compose'sText(). That per-platform mapping is exactly (4)/(5)-shaped platform data — it's real, it already varies today across the platforms that exist, and it's precisely what this RFC's boundary is meant to keep out of the shared taxonomy.The proposed state split is RFC-A's (#832) own distinction, propagated one layer up
RFC-A §3.3 already identified that
stateconflates two things:That's
states[].trigger("prop"vs"interaction") on the component declaration, which this RFC does not change. What this RFC proposes is taking that already-accepted distinction and applying it to the token side of the same cross-reference:statewould split intointeraction(transient, competing) andinteraction-context(persistent, composing), so a token can express the same compose-vs-compete semantics RFC-A already gave components, instead of flattening it back into one array at the point of reference.What this isn't saying
Some vocabulary overlap between component data and taxonomy is fine and expected (e.g.
container/controlas both a structure/element term and a component's own anatomy label) — that's a case-by-case call, not a violation. This also isn't about renaming any current component option. It's about what's allowed intopackages/design-data/fields/andregistry/going forward.Open questions / follow-up work
select-box.json,stack-item.json,tree-view.json) currently declarestates[].selectedwithtrigger: "interaction", contradicting RFC-A's ([RFC-A] Component Contract in Design Data Spec #832) §3.3 example (isSelectedis explicitly listed as a composing, prop-driven state). Should be corrected totrigger: "prop"regardless of this RFC's outcome — a direct, textual application of RFC-A, not a judgment call. Two more candidates were checked and are not clear errors:breadcrumbs.json'sdragastrigger: "prop"(a plausible per-component implementation choice) andmenu.json'sdefaultastrigger: "prop"(the canonical vocabulary table instate-model.mddoesn't assigndefaulta trigger at all).trigger. Ifstatesplits intointeraction/interaction-context, that gap becomes concrete rather than latent (a token could put atrigger: "prop"state name ininteraction, or vice versa, and nothing would catch it). Would need a new SPEC rule cross-checking bucket placement againsttrigger, mirrored for CTRs the way SPEC-054 mirrors SPEC-022 today.states.jsonshould carry the existingplatformsnotes (iOShover→highlighted,keyboard-focus→focused) forward into theplatform-extensions/mechanism rather than dropping them.docs/proposals/002-component-property-axes.md(still Draft) proposes importing component-schema-specific enums (style,staticColor,isEmphasized) directly into the universal taxonomy — exactly the conflation this RFC and Token Taxonomy, Vocabulary, and Formatting — Naming Convention Decomposition #806 argue against. Flagging for re-examination, not asserting it's wrong outright.All reactions