The 3-tier token architecture
Our token system is built on three layers, where each tier references the one below it:Tier 1 — Primitives
Raw, context-free design values. These are the foundation — color scales, spacing, radii, and typography values with no semantic meaning.Tier 2 — Semantic tokens
Purpose-driven tokens that reference primitives and include light/dark mode support. These answer the question “what is this color for?” rather than “what color is it?”Tier 3 — Component tokens (new in 3.0)
Component-specific tokens that reference semantic tokens. Each component’s visual properties — foreground, background, and border colors across all variants and states — are defined through dedicated token files.How the theming cascade works
Tier 3 tokens enable two levels of customization:Bulk theming via tier 2 overrides
Override a semantic token and every component referencing it updates automatically. Change--a2-background-color-primary and Button, Chip, Tab, and any other component using that semantic meaning all pick up the new value.
Targeted component overrides
Override a specific component token to customize just that component without touching anything else. Change--a2-button-primary-background-color and only Button is affected.
Override method 1: CSS variable overrides
Set tier 2 semantic tokens directly as CSS custom properties via inline styles:Override method 2: ThemeProvider (recommended)
Use theThemeProvider component’s theme prop for a type-safe, structured approach. ThemeProvider accepts theme.semantic and theme.component objects and handles light/dark mode values automatically:
We recommend using
ThemeProvider for token overrides. It generates the correct CSS custom properties for you, handles light/dark mode automatically, and provides type safety through the CustomThemeType interface.What tier 3 covers today
Tier 3 currently covers color tokens only — foreground, background, and border colors for each component’s variants and interactive states (hover, active, disabled). All stable (non-beta) components now consume tier 3 tokens when defined. We ship 47 component token files with 3.0. Each file follows the same predictable naming convention:{component}.{variant}.{property}.{attribute}. For example, button.primary.background.color-hover or listbox.option.selected.foreground.color.
CSS variable cascading: what works and what doesn’t
An important caveat about runtime CSS variable overrides. Overriding a tier 1 primitive (e.g.,--a2-color-blue-600) does not cascade to tier 2 or tier 3 tokens at runtime.
This happens because the build process inserts the light-dark() CSS function at the semantic tier to support dark mode. The light-dark() function creates isolated branches that inline resolved values rather than preserving live var() references back to primitives.
light-dark() resolves its branches independently, overriding --a2-color-blue-600 at runtime does not propagate upward.
What does work:
- Override tier 2 (semantic): Set
--a2-background-color-primaryto affect all components referencing that semantic token - Override tier 3 (component): Set
--a2-button-primary-background-colorto customize a single component
ThemeProvider for overrides. It accepts theme.semantic and theme.component objects that correctly generate the CSS variable overrides for you, handling light/dark mode values automatically. This is the safest and most reliable way to customize token values.
What’s coming next
This release establishes the tier 3 architecture for colors. In future releases, we plan to expand tier 3 to cover additional properties:- Border radius — component-specific corner rounding
- Padding and spacing — internal component spacing
- Other visual properties — as the system matures
Breaking changes to tier 2 tokens
If you use Anvil2 components out of the box without custom token overrides, these changes do not affect you — components already consume the updated tokens internally. However, if you reference tier 2 tokens directly in custom components or overrides, review these changes: Overlay tokens removed — interactive states now use direct background colors. In 2.x, interactive states (hover, active) were implemented using a pseudo-element with an overlay color layered on top of the base background. In 3.0, each background color token now defines its own interactive state variants directly (e.g.,background.color.primary-hover, background.color.primary-active). This eliminates the need for the overlay token category entirely:
| 2.x | 3.0 |
|---|---|
overlay.color.hover.default | background.color.transparent.default-hover |
overlay.color.active.default | background.color.transparent.default-active |
overlay.color.hover.primary | background.color.transparent.primary-hover |
$root pattern removed. Interactive states are now flat hyphenated siblings instead of nested children:
For the full migration guide, see Migrating from 2.0 to 3.0.
Questions, feedback, or ideas? Drop us a line in #ask-designsystem.