In a shared component library, why should a component's styles reference semantic theme tokens instead of literal values such as a raw color code?
answer
- role versus raw value
- who decides the value, and where
- themes reach only what references them
- reviewers read intent, not numbers
- structural values may stay literal
basics
~20 sA literal freezes one decision inside the component, so no theme, mode or consumer can change it. A semantic token names the role a value plays, such as selected surface, and lets the active theme supply the value.
solid answer
~50 sA literal such as a raw color code freezes one visual decision inside the component, so nothing outside can reach it: not a new theme, not a color mode, not a consuming team with a legitimate need. Referencing a **semantic token** — a name for the role a value plays, such as `surface.selected` or `text.on-action` — splits the job in two: the component says *which role* each part plays, and the active theme says *what value* that role has. The same component then renders correctly under every theme the system ships without being edited, reviewers read intent instead of numbers, and near-duplicate values stop multiplying. Native mobile components work the same way, reading role names from a theme object. Not every number needs a token — structural values no theme should vary can stay literal.
go deeper
Recall the split: the component names a role, the theme supplies the value. Be able to say what breaks when a raw color is typed into a component.
Explain why state styles need roles too, why appearance-named references fail under a new theme, and which structural values can honestly stay literal.
Show how you would find and remove literals in a live library without breaking apps, using lint rules, a garish test theme and a path for requesting new roles.
Frame literal-free components as what makes one library serve many brands, modes and platforms, and weigh the governance cost of adding roles on request against letting literals in.
## What a theme reference is A **design token** is a named design decision — a color, a spacing step, a corner radius — stored once and consumed by name. A **semantic token** names the *role* a value plays in the interface (`surface.selected`, `text.on-action`, `border.focus`, `feedback.success`) rather than the value itself. A **literal** is a raw value typed straight into a component's styles: a color code, a fixed spacing number, a fixed radius. When a library component references a semantic token, it answers only one question: *which role does this part play?* The **theme** — the set of values an app has loaded — answers the other: *what does that role look like here?* The component is written once; the look is decided somewhere else. That separation is what lets one library serve many apps, several themes and more than one platform. ## What literals break - **Themes cannot reach them.** A theme is a lookup from role names to values. A part that names no role is invisible to every theme, so it keeps its old color when the rest of the page changes. - **Consumers cannot adjust them.** A team with a real need has no sanctioned lever, so it reaches into internals or forks the component. - **Meaning is lost.** A reviewer who sees a raw green cannot tell whether it means *selected*, *success* or *brand accent*, and cannot tell whether two greens are meant to be the same decision. - **Near-duplicates multiply.** Each hand-copied value drifts slightly; a year later the library holds a dozen almost-identical greens and nobody knows which is canonical. - **Platforms diverge.** A native mobile team building the same component from the spec reads the same role names; if the web version hard-codes values, the two drift apart silently. ## A worked example: amount chips on a donation page A charity donation site shows preset amount chips (10, 25, 50, other) above a fundraising goal meter. The charity later loads a seasonal appeal theme with different surfaces and accents. | Part | Literal version | Referenced version | After the appeal theme loads | |---|---|---|---| | Selected chip background | raw light-green code | `surface.selected` | literal stays green; reference follows the appeal theme | | Selected chip text | raw dark-green code | `text.on-selected` | literal may now clash; reference stays paired with its surface | | Chip border | raw grey code | `border.default` | literal stays grey; reference updates | | Goal meter fill | raw green code | `feedback.progress` | literal ignores the appeal; reference picks it up | The referenced version needed no edit. The literal version needed a library release — and every app pinned to the old release kept the old look. ## Choosing the reference 1. **Name the role, not the appearance.** The selected chip references *selected surface*, not *light green*: the appearance is exactly what themes are allowed to change. 2. **Reference the semantic layer, not raw palette entries.** How primitive and semantic tiers are structured is its own topic; from inside a component, the rule is simply to consume roles. 3. **Cover every state.** Hover, pressed, focus indicator and disabled are roles too; a component that references a token at rest but a literal on hover breaks the moment a theme changes. 4. **Ask for a missing role instead of inventing a literal.** If no role fits, the gap belongs to the system's owners, who can add one that other components can share. ## Keeping literals out - A **lint rule** over component style sources can flag raw color codes and unexplained numbers, with an allowlist for structural values. - A deliberately garish **test theme** makes literals obvious: anything that does not change color under it is not wired to the theme. - Code review asks one question per new style value: *which role is this?* ## Where a literal is fine Not every number must be a token. Values that no theme, mode or brand should ever vary — zero offset, full width, a square aspect ratio, the count of columns in a fixed layout — are structure, not design decisions, and wrapping them in tokens adds noise without flexibility. The test is not *how often is this value used* but *would any theme ever legitimately want it different?* If the answer is yes, it is a role and deserves a name.
- A designer says the donate chip's green will never change, so a literal is fine. How do you respond?The value may not change, but the context will: a seasonal theme, a color mode, a partner brand or a native port will each need that part to follow the theme. A reference costs nothing today and keeps the chip consistent with every other selected surface. If the value truly never varies, the semantic token simply keeps pointing at it — no one loses anything.
- How do you find literals already hiding in an existing library?Two cheap methods together. A lint rule scans component style sources for raw color codes and unexplained numbers, with an allowlist for structural values. Then render the library under a deliberately garish test theme: any part that keeps its old look is not wired to a token. Each hit becomes either a reference to an existing role or a request for a new one.
A theater script says 'the lead actor enters' rather than naming a specific actor; each production casts the role, and the script never needs rewriting for a new cast.
saying these in an interview costs you the question
- A literal is fine as long as it matches the current theme's value.
- Theming only concerns colors, so spacing and radius can stay hard-coded.
- Naming a palette color is as good as naming a role.
- Tokens exist mainly to make style code smaller or rendering faster.
- A theme can override a literal written inside a component.