Design systems and component APIs
Components read roles, not values; primitives carry behaviour; the version number says what may break.
Predict first
Before you read: predict
ExamHall's shared token build defines the primitive color.gray.0 (#ffffff) and the semantic color.surface, which aliases {color.gray.0}. The proctor console's Dialog sets background: var(--color-surface); its Card sets background: var(--color-gray-0). The dark theme's token file has one line for surfaces: it re-points color.surface to {color.gray.900} (#0f172a).
With data-theme="dark" on <html>, what backgrounds do the two show?
The 30-second version
A design system is three layers: tokens (named design decisions), accessible primitives (behaviour and ARIA, unstyled) and components built from both. Components read role tokens, so a theme is a remapping and one set serves both directions. The library is versioned and tested once for every app; a breaking change ships as a major, behind a deprecation.
Components read roles, not values; primitives carry behaviour; the version number says what may break.
How it works
Step 1 of 13
Analogy: A flag maker working to the national flag law (civic heraldry)
A country's flag law describes its flag, and flag makers in every town sew it, in cloth for town halls and schools and in print for paper programmes.
| Under the flag law | In the design system |
|---|---|
| the dye maker's shade book, each shade a fixed code | primitive tokens (step 2) |
| the law names each colour by its part, "the field" and "the star", and its annex maps each part to a shade; a second annex maps the same parts to the shades for printing on paper | role tokens and themes (steps 3, 5) |
| a maker who wrote the shade code itself on his pattern keeps that shade when the annex changes | a component reading a primitive (step 4) |
| the law says "hoist" (the pole side) and "fly", not left and right, so one description serves both faces of the flag, the back being the front's mirror image | one token set, logical properties (step 7) |
| a mistake in the annex reaches every maker who sews to the law, and so does its correction | fix once, break once (step 9) |
| when the annex replaces a shade, flags in the old shade stay lawful for a set period before only the new one is | deprecate, then remove (step 12) |
In real terms: apps share decisions, behaviour and a contract, not a picture of buttons.
Breaks down when: a flag is sewn once. A shared library is rebuilt into every app on every release, so a mistake in it reaches every app the next time each one updates. The law also gives no part behaviour to share: nothing in it maps onto accessible primitives (step 8).
Examples, run for real
4.1 ExamHall EXH-3420: dark mode skips the admin app
ExamHall's three apps share one token package. When dark mode shipped, the admin app's cards stayed white and its Approve button kept its light-theme blue: its CSS read var(--color-gray-0) and a hex copied from the design file (EXH-3420). MR !3720 moved the admin CSS onto role tokens. The build writes one file for every app:
:root {
--color-gray-0: #ffffff;
--color-gray-100: #f1f5f9;
--color-gray-600: #475569;
--color-gray-900: #0f172a;
--color-blue-400: #60a5fa;
--color-blue-600: #1d4ed8;
--space-2: 8px;
--space-4: 16px;
--space-6: 24px;
--color-surface: var(--color-gray-0);
--color-text: var(--color-gray-900);
--color-text-muted: var(--color-gray-600);
--color-action-bg: var(--color-blue-600);
--color-action-text: var(--color-gray-0);
--space-inset: var(--space-6);
}
[data-theme="dark"] {
--color-surface: var(--color-gray-900);
--color-text: var(--color-gray-100);
--color-text-muted: var(--color-gray-100);
--color-action-bg: var(--color-blue-400);
--color-action-text: var(--color-gray-900);
}
EXH-3420 dark theme, candidate app (role tokens): card #0f172a · Approve #60a5fa
EXH-3420 dark theme, admin app as found (primitive token, copied hex): card #ffffff · Approve #1d4ed8
MR !3720 dark theme, admin app on role tokens: card #0f172a · Approve #60a5fa
one token file, four contexts · light ltr: Approve #1d4ed8, padding left 24px right 16px · light rtl: Approve #1d4ed8, padding left 16px right 24px · dark ltr: Approve #60a5fa, padding left 24px right 16px · dark rtl: Approve #60a5fa, padding left 16px right 24px
The admin app changed no token; it changed which names it reads (steps 4–5). The Button's padding uses padding-inline: var(--space-inset) var(--space-4), so the same tokens put the wide side at the reading start in Arabic (step 7).
4.2 Payments ledger MR !3721: one primitive, three apps
A payments ledger's library ships an icon-only IconButton that put its label in a tooltip attribute only. Its wallet, back office and merchant dashboard all render it. MR !3721 also passes label as the button's aria-label, in the primitive alone:
IconButton 2.0.0: axe button-name violations · wallet 3 · back office 5 · merchant 2
IconButton 2.0.1 (MR !3721): axe button-name violations · wallet 0 · back office 0 · merchant 0
No app's code changed; each took the fix by updating to 2.0.1 (steps 9, 13).
Picture it

Try it
Answer first, then check. Nothing is saved.
Question 1 of 8
A payments ledger's design system serves three apps: the customer wallet, the back office and the merchant dashboard. All three put white text on their blue primary buttons. The wallet team asks for "a friendlier blue", and MR !3722 to the shared tokens changes one line of the light set: color.action-bg now aliases {color.blue.400} (#60a5fa) instead of {color.blue.600} (#1d4ed8). Three reviewers commented.
Which comment holds once the three apps are rebuilt and checked?
Question 2 of 8
A ticketing company's design system ships a Select used by the box-office app, the venue console and the admin tool. Each app's build type-checks its call sites against the library's published types (TypeScript 6.0.3). Release 2.5.0 adds size: 'sm' | 'md' to SelectProps, without a ?; the component's code reads it as { size = 'md', ...rest }, so a missing size would render as 'md'. Each app updates to 2.5.0, and in the same change the box-office app passes size at both of its call sites, the admin tool at one of its three, the venue console at none of its three.
Whose builds fail?
Question 3 of 8
The proctor console shows a dark sidebar inside a light page, <aside data-theme="dark">. Its CSS reads:
:root { --color-gray-0: #ffffff; --color-gray-900: #0f172a; --color-surface: var(--color-gray-0); }
[data-theme="dark"] { --color-gray-0: #0f172a; }
A Card in the main area and a Card inside the aside both set background: var(--color-surface).
What backgrounds do the two Cards show?
Question 4 of 8
A streaming app's design system needs a shared Select for its settings screens (audio language, subtitles), in English and Arabic. It will wrap React Aria's Select as its primitive and style it with its own tokens.
Which split of the work matches what React Aria says it provides?
Question 5 of 8
The interviewer hands you canvas/start/: ExamHall's token file, which holds primitives only (tokens.json), an empty dark file (tokens.dark.json) and components.css, where the Button, Card and Dialog use hex values, primitive tokens and padding-left. "Three apps will share these. Add light and dark themes and Arabic, without a second copy of the components." You have 15 minutes.
Sketch the layers first (which file holds what, and what each app imports), then edit the three files.
Question 6 of 8
The interviewer asks: "Tell me about a breaking change you shipped in a shared component." Use this case: a payments ledger's Button takes kind="primary"; the team wants variant="primary". Its 400 call sites sit in three apps owned by three teams.
Write the story in five to eight sentences: what you released, in what order, how the call sites moved, and how you knew it was done.
Question 7 of 8
A chat company's design system fixes a missing label in its shared MessageInput and publishes 3.2.1. Before it, the published versions ended at 3.1.7 and 3.2.0. The web app depends on ^3.1.0, the desktop app on ~3.1.4, the admin tool on exactly 3.2.0. Each app runs npm update.
Whose message box passes axe's label rule afterwards?
Question 8 of 8
A payments ledger's design system has run axe on each primitive in its light theme only, and the Badge passes. The Badge draws color.text-muted on color.surface. In the light set color.text-muted aliases {color.gray.600} and color.surface aliases {color.gray.0}. The dark set re-points color.surface to {color.gray.900} and leaves color.text-muted as it is. CI now opens the Badge with data-theme="dark", runs axe, then sets data-theme="light" on the same page and runs axe again.
What do the two runs report?
Go deeper
Pro tips
- Keep the alias chain visible in the built CSS. Build with Style Dictionary's
outputReferences, and the file shows which primitive each role names, so a review of a theme diff reads as role changes. When: reviewing a theme change or hunting a hard-coded value. Verify (RAN,tips.mjs):tip 1 outputReferences true: --color-text: var(--color-gray-900); tip 1 outputReferences false: --color-text: #0f172a; - Make a dangling alias fail the build. Configure the token build to throw on a broken reference, so a role pointing at a deleted primitive stops the release instead of shipping an empty custom property. When: deleting or renaming primitives. Verify (RAN,
tips.mjs):tip 2 color.text -> {color.gray.900}, primitive deleted: build throws · Reference Errors: / Some token references (1) could not be found.
Source
Valid as of 2026-10-10.
- React Aria, home page (the curriculum row's second source), https://react-spectrum.adobe.com/react-aria/ (
react-aria.txt). "Components implement semantics and keyboard behavior according to the W3C ARIA Authoring Practices Guide." and "React Aria is style-free out of the box, allowing you to build custom designs to fit your application or design system using any styling and animation solution."; "Keyboard interactions are first-class, including arrow key navigation, typeahead, multiple selection modifiers, landmark navigation, and much more."; "React Aria is engineered for internationalization out of the box, including translations in over 30 languages, localized date and number formatting and parsing, support for 13 calendar systems, 5 numbering systems, right-to-left layout, and more.". - W3C WAI, ARIA Authoring Practices Guide (the row's third source), https://www.w3.org/WAI/ARIA/apg/ (
apg-home.txt): "This guide describes how to apply accessibility semantics to common design patterns and widgets." (step 8). Its Read Me First, https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/ (apg-read-me-first.txt), read for the prerequisite's passage only: "Unlike HTML input elements, ARIA roles do not cause browsers to provide keyboard behaviors or styling." (step 8 relies on it;ui-semantics-ariastep 2 teaches it). - Design Tokens Community Group, Design Tokens Format Module 2025.10 (the row's first source is www.designtokens.org; the site did not answer on 2026-10-10, so the module's published text was read from the group's own repository, commit 882ebd6,
www/src/pages/TR/2025.10/format/index.html;dtcg-format.txt): "Design tokens are a methodology for expressing design decisions in a platform-agnostic way so that they can be shared across different disciplines, tools, and technologies." (step 1); "A design token's value can be a reference to another token. The same value can have multiple names or aliases." (step 3); "Aliases MAY reference other aliases. In this case, tools MUST follow each reference until they find a token with an explicit value." (step 3). The draft Resolver Module introduction, same repository (dtcg-resolver-intro.txt): "such as light mode, dark mode, and high contrast color modes" and "these alternate contexts are susceptible to" (steps 5, 7). Valid as of 2026-10-10; the Resolver Module is a draft. - W3C, CSS Custom Properties for Cascading Variables Module Level 1, https://www.w3.org/TR/css-variables-1/ (
css-variables-1.txt): "It is important to note that custom properties resolve any var() functions in their values at computed-value time, which occurs before the value is inherited.". - npm, node-semver README, https://raw.githubusercontent.com/npm/node-semver/main/README.md (
node-semver.txt): "Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not." and "Allows changes that do not modify the left-most non-zero element in the[major, minor, patch]tuple.".
Related lessons
- Read first: Semantic HTML, the accessibility tree and ARIA
- Read first: CSS cascade layers
- Read first: RTL layout and logical properties
- Related: UI build: accessible modal with a focus trap
- Related: Compound components: parts that share a Root's state
- Related: Headless components: behaviour and ARIA without markup
- Related: Controlled and uncontrolled component APIs
Checked against the tools and docs as of 2026-10-10.