skip to content

In a game companion app, a shared scoreboard component looks right in the app theme but breaks inside a faction-themed panel; what usually causes this, and how does a design system prevent it?

level: seniorimportance: should knowfreq 36%

answer

  1. what the component assumed
  2. raw values leak past the theme
  3. app-level lookup, not nearest scope
  4. branching on a theme's name
  5. render every component in every scope

basics

~20 s

The component assumed one theme: it hard-codes raw values, reads the app-level theme instead of its nearest scope, branches on a theme name or uses baked-color images. Prevent it with semantic-only references, nearest-scope resolution and tests rendering each component in every theme and scope.

solid answer

~50 s

A component that works in one theme and breaks in another has usually made an assumption the theme cannot reach: a **raw value** such as a fixed color or shadow, a lookup of the **app-level** theme instead of the nearest scope, a branch on the **theme's name**, an image or icon with **baked colors**, or a transparent area that assumes a dark or light background behind it. The design-system rule is that shared components are **theme-agnostic**: they reference only semantic values, resolve them from the nearest scope, and never test which theme is active. The system enforces it with lint rules against raw values and theme-name checks, and with a test matrix that renders every component in every theme and inside a contrasting nested scope, so the scoreboard's failure shows up in review rather than in the app.

go deeper

for a junior

Recall the rule: a shared component uses only semantic values and never asks which theme is active.

for a middle

Explain why raw values, app-level lookups and theme-name checks each escape the theme, and how nearest-scope resolution fixes the lookup.

for a senior

Diagnose the specific assumption, fix it in the shared component, sweep sibling components and add nested-scope stories to visual regression.

for a principal

Decide how the system enforces theme-agnostic components at scale, lint, workshop matrix, review, and what that enforcement costs contributing teams.

## The symptom In a multiplayer game's companion app, the shared **scoreboard** component looks right on the main screen. Placed inside a faction-themed panel, its row dividers vanish, one column's text is unreadable and the team badges keep the app's colors. Nothing is wrong with the panel's theme; the component has quietly assumed it would always be in the app theme. ## Why components break under other themes A theme can only change what a component asks it for. Every value the component does not ask for is outside the theme's reach. The usual assumptions: 1. **Raw values.** A divider color, a shadow or a text color written as a literal value instead of a semantic token. It is right in one theme by coincidence. 2. **The wrong lookup.** The component reads the app-level theme directly instead of resolving from its nearest scope, so it ignores the panel's sub-theme. 3. **Branching on a theme's name.** Logic such as *if the theme is blue, use this* works for the themes that existed when it was written and fails for every later one. 4. **Baked-color assets.** Badges or icons exported with fixed colors cannot follow any theme. 5. **Background assumptions.** A transparent area that was designed over a dark surface becomes unreadable over a light one. 6. **Local overrides.** A consuming team pinned a value to fix one screen, and the pin travels with the component. ## The rule: theme-agnostic components A shared component should render correctly **in any theme and any scope** the system supports, without knowing which one it is in. That means: - it references **only semantic values** for anything a theme may change; - it resolves them from the **nearest themed scope**; - it **never inspects** which theme is active; - its icons and images take their color from semantic values or ship per-theme variants chosen by the theme, not by the component; - it assumes nothing about the surface behind it beyond what its own semantic background provides. ## How a design system enforces it | Guard | What it catches | |---|---| | Lint rule against raw values in component styles | literal colors, shadows and radii | | Lint or review rule against theme-name checks | branching on a specific theme | | Component workshop stories in every theme | anything that only works in the default | | A nested-scope story (component inside a contrasting sub-theme) | app-level lookups and background assumptions | | Visual regression per theme | dividers, borders and icons that lose contrast | | Contrast checks per theme | text and non-text pairings that fail | The nested-scope story matters most: a component tested in each theme at the root can still read the app-level value, and only placing it inside a *different* inner theme exposes that. ## Fixing the scoreboard 1. Replace every literal value with the semantic token for its role: divider, text-secondary, surface-raised. 2. Resolve from the nearest scope rather than the app root. 3. Remove any check on the theme's name; if two themes need different behaviour, that difference belongs in their values. 4. Make team badges take their color from semantic or faction values supplied by the theme. 5. Add workshop stories for the scoreboard in every theme and inside a contrasting sub-theme, and add them to visual regression. 6. Search for the same patterns in sibling components; the scoreboard is rarely the only one. ## Native mobile The same failure occurs in native component libraries: a view styled with fixed resource values, or one that reads a global theme object instead of the one supplied to its part of the hierarchy, breaks the same way. The rule and the test matrix carry over unchanged.

  • Two themes genuinely need the scoreboard to behave differently. Where should that difference live?
    In the themes' values, not in the component's logic. If one faction needs a heavier divider, the theme supplies a heavier divider value for the same semantic name. If the difference cannot be expressed as values, it is probably a component variant chosen by the product, not by the theme.
  • Why is testing each theme at the root not enough?
    A component that reads the app-level theme passes every root-level test, because at the root the app theme and the nearest scope are the same. Only placing it inside a contrasting sub-theme reveals that it ignores its scope, so the test matrix needs a nested-scope case.

saying these in an interview costs you the question

  • A literal color is fine if it matches the current theme.
  • Components should check the theme's name to adjust themselves.
  • Testing each theme at the root proves components respect scopes.
  • Baked-color icons are fine because icons are not themed.
  • Fix it by adding a panel-specific scoreboard variant.