Accessibility · Inclusive Design
Encoding Accessibility Constraints in a Design System Token Layer
Why constraints enforced by review decay while constraints encoded in tokens and components persist, which properties belong in the token layer, and what the approach cannot express.
A constraint that lives in a review checklist is enforced by attention, and attention is a resource that runs out. The tenth screen reviewed on a Friday afternoon gets less scrutiny than the first, the reviewer who knows the requirement leaves the team, and the checklist itself is versioned in a document that nobody reads. The same constraint, encoded in the components that teams actually use, is enforced by construction: a component that cannot render a target below the minimum cannot produce a violation, no matter who is reviewing.
That is the case for moving accessibility constraints into the design system. The case is strong for a specific subset of constraints and weak for others, and the boundary is worth drawing precisely, because a design system that claims to enforce accessibility and only enforces part of it produces a false sense of coverage.
What the token layer can hold
Design tokens are named, platform-independent values: a spacing scale, a type scale, a colour palette, a radius, a duration. Their power is that a decision made once propagates to every consumer, and their limit is that they can only express values, not rules.
The constraints that translate cleanly into tokens are the ones that are themselves numeric bounds.
| Constraint | Token expression | What it enforces |
|---|---|---|
| Minimum target size | A spacing step used as the component’s padding box, with a minimum enforced in the component | No control smaller than the floor |
| Minimum separation | A gap step between adjacent interactive elements | Discrimination between neighbours |
| Minimum body text size | A type-scale step, with the scale ratio bounded | Legibility floor |
| Line height for body text | A unitless multiplier token | Readability at any text size |
| Focus indicator thickness and offset | Tokens consumed by the focus ring mixin | Visibility against any background |
| Contrast pairs | Paired foreground/background tokens that are only used together | No accidental low-contrast combinations |
| Maximum animation duration for essential motion | A duration scale with an upper bound | Motion that can be tracked |
| Minimum hit area for icon-only controls | A component-level size token | Coarse-pointer operation |
Two of these deserve a note. The contrast pair is the most valuable token type in the list, because contrast is the property most often broken by composition rather than by choice: each colour in isolation was chosen correctly, and the pair was never checked. A palette expressed as approved pairs removes the possibility of an unapproved combination, at the cost of a larger palette. The focus indicator token is the second most valuable, because a focus ring is the one element that must be visible against arbitrary backgrounds and is usually defined once and forgotten.
:root {
/* spacing scale; the smallest interactive padding step is 12px,
which combined with a 24px glyph yields a 48px box */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
/* type scale; body starts at 16px and steps by a bounded ratio */
--font-size-body: 1rem;
--line-height-body: 1.5;
/* focus ring: two-tone so it survives both light and dark surfaces */
--focus-ring-width: 2px;
--focus-ring-offset: 2px;
--focus-ring-inner: #ffffff;
--focus-ring-outer: #101828;
}
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-outer);
outline-offset: var(--focus-ring-offset);
box-shadow: 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width))
var(--focus-ring-inner);
}
The two-tone ring is the mechanism worth explaining: a single-colour outline is invisible against backgrounds of the same colour, and the pairing of a light inner ring with a dark outer ring guarantees that at least one of the two contrasts with any surface. This is a constraint expressed as a composition rule, and it is the kind of rule a token layer can hold.
What belongs in components rather than tokens
A component can enforce things a token cannot, because a component has behaviour.
A button component can enforce a minimum box size regardless of the icon it contains, so that an icon-only variant cannot shrink below the floor. A field component can enforce that its label is associated with its input, that its error message is associated with the input as a description, and that the error is announced when it changes. A dialog component can enforce focus containment and restoration. A list component can enforce that its reordering does not desynchronize the DOM sequence from the visual sequence, which is the linearization constraint that CSS ordering properties routinely break.
The general form of a component-level constraint is: the component makes the correct implementation the only one available, and the incorrect one impossible or awkward. A button whose accessible name is required as a prop cannot be shipped unnamed. A field whose label is a required slot cannot be shipped unlabelled. An icon component that requires either an accessible name or an explicit decorative flag cannot be shipped ambiguous.
The awkward cases are the ones where the constraint requires information the component does not have. A component cannot know whether the text it is given is understandable, whether the heading level it is placed at is correct for the document outline, or whether the action it triggers is reversible. Those are semantic judgements, and no amount of component API design removes them. Pretending otherwise is the false-coverage failure.
The token layer’s structural limits
Four constraints resist encoding, and each has a different reason.
Constraints that depend on context. Contrast is a property of a pair, and the pair may be assembled at a call site the token layer does not control. A text colour token used over an image is a contrast failure the token cannot see. The mitigation is to restrict text over images to a component that guarantees a scrim, which converts the contextual constraint into a component constraint.
Constraints that depend on content. Whether a link’s text describes its destination is not a property of the link component. Whether an error message explains how to fix the problem is not a property of the field. These are authoring requirements, and the only enforcement mechanisms are linting against patterns and review against examples.
Constraints that depend on the whole page. A page must have one main landmark, a coherent heading outline, a title, and a language. None of these is a component; all of them are properties of the composition. A design system can provide the pieces and cannot assemble them, which is why page-level templates are a better enforcement point than components for this class.
Constraints that depend on time and state. A live region must announce a change without stealing focus. A loading state must not remove the content the user was reading. A component can hold the mechanism, but whether the announcement is useful depends on the text it is given and on when the change occurs, which is a behavioural property of the application rather than of the design system.
The honest summary is that the token layer covers the numeric floor, the component layer covers the structural requirements, and the remaining set is authoring guidance. A design system that documents which of the three layers owns each constraint is more useful than one that claims to enforce everything.
Changing a constraint after it has propagated
A constraint encoded in a token is not permanent, and the day it changes is the day the encoding either pays off or becomes a liability.
Raising a minimum target size from 44 to 48 pixels is one edit in the token layer and one release for every consumer that upgrades. That is the payoff. The liability appears when consumers do not upgrade, because the old value is still in the tree and the two values now coexist: a component from the current version rendered inside a page still using the previous stylesheet. The symptom is a control that is 48 pixels in one region and 44 in another, which is not a defect anyone will attribute to a version skew.
The remedy is to treat the numeric constraint as part of the component’s contract rather than as a value consumers may pin independently. A component that computes its own minimum box from its own spacing tokens cannot be split from the constraint by a stale stylesheet. Where the value must remain a separate token, the old name is kept as an alias for one release, marked deprecated, and removed on a stated schedule; the alias exists so that the migration can be mechanical, not so that the old value survives.
Deprecation is where the escape-rate metric from the next section becomes an operational tool. A search for the deprecated token name enumerates every consumer that has not migrated, which converts a vague “some teams may still use it” into a list. The same search finds hard-coded literal values that should have been tokens, and the two lists together are the migration backlog.
Version skew has a second, less obvious cost. A constraint that is enforced in the component layer can be defeated by a consumer that overrides the component’s styles from outside, and the override is usually a one-line exception made under deadline. Overrides are legitimate when the design genuinely differs; they are illegitimate when they exist because the component could not express a variant. The distinction is visible in the override’s content: an override that changes a colour is a design decision, and an override that shrinks a target is a violation of the constraint that the component was built to hold.
Adoption is the actual constraint
A token layer enforces nothing if teams bypass it, and teams bypass it for ordinary reasons: the token does not exist for the value they need, the component does not support the variant the design requires, or the deadline is close and a local stylesheet is faster.
The design response is to make the correct path the shortest one. That means the token scale must be wide enough to cover the values designs actually need, with the constrained steps placed at the boundaries so that the middle of the scale is comfortable and the extremes are deliberate. A scale with a gap where a common value should be invites a hard-coded value. A component without the variant that appears in three designs invites three local forks.
The measurement that matters is the rate of escape: how much styling exists outside the token layer, and what it is used for. A codebase where every colour comes from a token and every interactive element comes from a component is enforcing its constraints; a codebase where the tokens exist and a third of the styles bypass them is enforcing them in the third that happened to be migrated. Escape rate is measurable by searching for literal values in stylesheets and for raw elements in templates, which makes it a metric that can be tracked rather than a quality that can be asserted.
The economic argument for doing this work is the same marginal-cost argument that motivates building constraints in early: a constraint applied at a definition costs one decision, and the same constraint applied at ten thousand instances costs ten thousand checks. The design system is the mechanism that moves the cost from the instances to the definition, and its coverage is exactly the set of constraints that can be expressed at that level.
References
- Design Tokens Format Module — Design Tokens Community Group, W3C
- Web Content Accessibility Guidelines (WCAG) 2.2 — W3C
- Inclusive Design Principles — Inclusive Design Principles project
- CSS Custom Properties for Cascading Variables Module Level 1 — W3C