Skip to main content
Anvil2 3.0 ships tier 3 component tokens, completing our 3-tier design token architecture. If you use Anvil2 components out of the box, this requires no changes on your end — components already consume the new tokens internally. For teams that customize component styles or build custom themes, tier 3 tokens give you fine-grained control over individual component colors through a predictable cascade — override a semantic token and every component that references it updates automatically, or target a single component without affecting anything else. This post walks through the architecture, shows how the cascade works with code examples, and covers what you need to know as a consumer.

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.
The full resolution chain for a button’s primary background looks like this:

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:
Use the ThemeProvider 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.
Because 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-primary to affect all components referencing that semantic token
  • Override tier 3 (component): Set --a2-button-primary-background-color to customize a single component
Our recommendation: Use 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.
This tradeoff exists because light-dark() insertion is necessary for dark mode support. The current design intentionally optimizes for dark mode correctness. Override at tier 2 or tier 3 — not tier 1.

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
This will give teams even more granular control over component styling through the same predictable token cascade.

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: $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.
Last modified on April 21, 2026