Token contract
Theme architecture from the design guidelines. Read it with the two governing rules and the compatibility contract.
2. Theme architecture
Figma and code describe the same system. Figma stores values in variable collections and modes. In code, the tokens build (#2414) stores tokens in three DTCG 2025.10 tiers and selects between contexts with a DTCG resolver file. This section maps one onto the other. Ownership follows the epic (#2412, decision 2): Figma owns values; code owns names, tiers, modes and component anatomy.
2.1 Three token tiers
| Tier | What it holds | Figma source | Code surface (provisional until #2414) | Who may read it |
|---|---|---|---|---|
| Primitive | Raw values only: colour ramps (oklch with a hex fallback), dimensions, durations, font families. Radix 12-step ramps (gray, slate, sand, mauve, blue, violet, orange, indigo, tomato, lime, grass, amber, red, teal) and theme ramps (ember, band, paper, glass, shadow, swiss-red) |
Primitives collection (Value mode) |
Internal custom properties, e.g. --ns-band-* |
The semantic tier only. Components never read primitives |
| Semantic | Aliases only. Every value references a primitive (or another semantic role), never a raw hex, px or ms. Core roles plus each style's namespaced roles (§2.4) | Scheme collection (Light, Dark). Its aliases are selected through the Theme set (Theme or Theme B), Base gray, Base accent, Density and Studio flavor modes (§2.2) |
--ns-* (core) and --ns-<theme>-* (styles) |
Component tokens and L2 component CSS |
| Component | Part and state tokens (button.*, input.*, card.*, …) that alias semantic roles, so heavy customization is a token edit, not a CSS override |
Variable bindings on library components; there is no component collection yet | Named by the tokens build from the component anatomy spec (#2416) | That component's own CSS only |
- Derived values. Tints, hovers and other mixes are not raw values in the semantic tier.
$extensions.netscript.derivedescribes them. CSS keepscolor-mix(), and Figma receives the computed value. - Collections and modes are not tiers.
Scheme,Theme,Theme B,Base gray,Base accent,DensityandStudio flavormodes are contexts the resolver chooses between (§2.2).Theme setonly switches the aliases betweenThemeandTheme B. None of them adds a fourth tier. - Raw values inside Figma modes. Some Theme modes still store raw values in slots that are
semantically aliases, for example Tactile's
surface/{l,d}/*and Fluent'saccent/{l,d}/solid. Each theme'sDESIGN.mdrecords them. Before export, the tokens build (#2414) splits each one into a primitive and an alias. - Node-level exceptions are Figma-only. A primitive or raw value bound directly on a Figma node
is a temporary exception. The theme's
DESIGN.mdnames the role that replaces it, pending in #2414. It never ships in L2 component CSS. - Layer ownership is unchanged. L0/L1 own platform behaviour, attributes and prop getters. L2 registry components consume component and semantic tokens. An L2 component never imports another L2 component.
2.2 How Figma collections map to the resolver
| Figma collection (modes) | What it selects | Token resolver (#2414) | Root attribute (provisional) |
|---|---|---|---|
Scheme (Light, Dark) |
The semantic role values for each scheme | scheme modifier: light, dark |
data-scheme |
Theme (Studio, Ledger, Filigrane, Signal, Tonal, Glass, Brutal, Fluent, Tactile, Pixel) |
Language values: fonts, type scale, radii, materials, shadow recipe, ring | theme modifier, with one context per language preset (§2.3), not one per Figma mode |
data-theme |
Theme B (Bloom, Cupertino, Liquid, Terminal, macOS 27, Apple Classic, Fluent 2 Web) |
Language values, as for Theme |
The same theme modifier |
data-theme |
Theme set (A, B) |
Which Theme collection the semantic roles alias through: A selects Theme, B selects Theme B |
Not a modifier; the theme modifier covers both Theme collections |
— |
Density (Comfortable, Compact, Dense, Airy, Chunky) |
Control, page, section, card, table and badge spacing | density modifier. The token resolver specifies comfortable and compact only; dense, airy and chunky need a resolver schema change |
data-density |
Studio flavor (Utility, Cloud, Workshop, Playful, Ledger, Atelier, Signal) |
Personality on top of the Studio language | Not a modifier. Each flavor expands into a Studio preset | data-flavor (Studio only) |
Base gray (Neutral, Slate, Sand, Mauve) |
Which gray ramp a theme sits on | Not a modifier. A preset parameter | data-gray |
Base accent (Ink, Blue, Violet, Orange, Indigo, Tomato, Lime) |
Which accent ramp drives primary/* |
Not a modifier. A preset parameter | data-accent |
Primitives (Value) |
Raw ramps | The primitive tier; no modifier | — |
This Figma plan allows 10 modes per collection, and Theme uses all 10. A second collection,
Theme B, holds the modes for Bloom, Cupertino, Liquid and Terminal, and for the planned macOS 27,
Apple Classic and Fluent 2 Web themes (#2443). The semantic roles alias through the Theme set
collection: set A selects Theme and set B selects Theme B. Each language has its own mode.
The Theme B modes were seeded with copies of existing values: Bloom from Tonal; Cupertino, Liquid,
macOS 27 and Apple Classic from Glass; Terminal from Signal; and Fluent 2 Web from Fluent. Each mode
can diverge. Until a language's own values are set, its mode resolves to its seed values, which are
prototype evidence only. Before a language ships, its values must come from its own mode, and the
token export (#2414) must give each language preset its own complete core-role map. When both
collections are full, a new language replaces a mode or needs a new Theme collection. The token
resolver (#2414) has no mode limit in code.
2.3 Supported resolver axes
The axes are scheme × theme × flavor × density × gray × accent. Not every permutation exists. The unit of support is the language preset: a fixed choice of Theme values, density, gray and accent (and, for Studio, flavor), designed and reviewed in both schemes.
| Axis | Values | Where it applies | Supported today |
|---|---|---|---|
scheme |
light, dark | Every preset | Both schemes for every preset; each dark is designed, not inverted |
theme |
studio, tonal, bloom, cupertino, liquid, fluent, brutal, filigrane, terminal, pixel, tactile |
— | One preset per language; Studio has seven, one per flavor |
flavor |
utility, cloud, workshop, playful, ledger, atelier, signal | Studio only | Each flavor fixes its own gray, accent and density (Studio DESIGN.md → Flavors). Every other language ignores it |
density |
comfortable, compact (token resolver); dense, airy, chunky (Figma only) | Studio flavors; a few languages set one explicitly | Fixed per preset; not a free user override yet |
gray |
neutral, slate, sand, mauve | Studio flavors | Fixed per preset. The other languages alias their own ramps inside their Theme mode and ignore it |
accent |
ink, blue, violet, orange, indigo, tomato, lime | Studio flavors | Fixed per preset, as for gray |
Precedence. The resolver applies theme (with flavor for Studio) as a preset that fixes
density, gray and accent. scheme then selects the Light or Dark values. An app's override layer
(#2414) applies last, on the semantic tier only. A preset ignores attempts to change an axis it
fixes, and the builder (§8) offers only supported combinations. A free axis, such as any density on
any language, needs a resolver schema change (#2414) and a contrast run for every new permutation.
Fixed axes per language. This table comes from each theme's DESIGN.md → NetScript Mapping.
The mode assignments are read from the Figma library and are not yet verified in code (#2414). Modes
in set B live in Theme B and still hold their seed values (§2.2).
| Language | Figma Theme mode | Density | Gray × accent |
|---|---|---|---|
| Studio | Studio |
per flavor (Utility: compact) | per flavor (Utility: neutral × ink) |
| Tonal | Tonal |
comfortable; M3 component sizes are fixed | mauve × violet, inside the mode |
| Bloom | Bloom (set B, seeded from Tonal) |
none (literal geometry) | none; Bloom reads bloom/* |
| Cupertino | Cupertino (set B, seeded from Glass) |
comfortable | slate × blue, inside the mode |
| Liquid | Liquid (set B, seeded from Glass) |
comfortable | slate × blue, inside the mode; own values in liquid/* |
| Fluent | Fluent |
none (WinUI defaults) | neutral gray × WinUI accent, inside the mode |
| Brutal | Brutal |
none (comfortable default) | sand × amber, inside the mode |
| Filigrane | Filigrane |
none | sand × tomato, inside the mode |
| Terminal | Terminal (set B, seeded from Signal) |
compact | gray × lime, inside the mode |
| Pixel | Pixel |
comfortable | sand-family × amber, inside the mode |
| Tactile | Tactile |
none | none; Tactile reads tactile/* |
Migration from today's selectors. Code today (NS One) scopes light on :root and dark on
[data-theme='dark']. The new model moves the scheme to data-scheme and gives data-theme to the
language: data-theme="dark" becomes data-theme="<language>" data-scheme="dark". Acceptance for
the tokens build (#2414) requires that existing --ns-* names keep resolving or have a documented
migration, and that NS One renders unchanged. The tokens build therefore either keeps
[data-theme='dark'] resolving as the default preset's dark scheme or ships that migration, with a
ui:migrate step for app-owned copies. Never rename an existing --ns-* name without a migration.
2.4 Styles and token-only presets
There are two kinds of theme. Keep them apart.
- Token-only preset. Assigns values to existing roles: a Studio flavor, a gray or accent choice, an app's brand theme. It adds no names and no component sources. Most app themes are this kind.
- Style. Every theme language (Studio, Tonal, Bloom, Cupertino, Liquid, Fluent, Brutal,
Filigrane, Terminal, Pixel, Tactile) is a style, in the sense shadcn uses the word: it restyles
every component, including shape, borders, fills, shadows, decorations and inner markup, over
one shared contract. A Pixel switch is Pixel's own switch, not the base switch with a smaller
radius.
- The contract does not change between styles (#2416): each component keeps its props,
data-partnames,data-variant,data-sizeanddata-statevalues, slots, APG pattern and keyboard map. The contract tests run against every style. - Tokens stay separate axes under every style: scheme, gray, accent, radius and density.
- A style declares:
- Its component sources: one per component, shipped as a registry
styleitem (#2415). - A role manifest: every namespaced role it adds, as
<theme>/<path>→--ns-<theme>-<path>, each with a Light and a Dark alias. Until the tokens build has a machine-readable manifest, the list is the Language roles row in the theme'sDESIGN.md→ NetScript Mapping. The tokens build registers every listed role before the style ships; unknown names fail CI. - A complete core map: every core semantic role still resolves under the style, so token-only presets and app-owned components keep working.
- Its component sources: one per component, shipped as a registry
- A style restyles its own components only, never app-owned markup outside them.
- The contract does not change between styles (#2416): each component keeps its props,
- In Figma, each style is a set of component sets named
<Style>/<Component>, with the same property names, values and layer names as the base set, so an instance swap keeps every override. Variables carry the token axes; variants carry states and sizes inside a style.
Styles with namespaced roles today: studio/*, liquid/*, tactile/*, bloom/*, cupertino/*,
fluent/* and pixel/*. Only that style's components read its namespaced roles. A Studio flavor is
a token-only preset inside the Studio style.
2.5 Core semantic roles (every theme defines all of them, in Light and Dark)
The CSS custom property comes from each variable's code syntax. For example, surface/bg maps to
var(--ns-bg) and surface/card to var(--ns-card).
- Surfaces:
surface/bg,surface/surface,surface/card,surface/raised,surface/muted,surface/hover,surface/selected,surface/overlay,surface/container,surface/container-high,surface/container-highest,surface/inverse. - Text:
text/fg,text/card,text/muted,text/subtle,text/inverse,ink/fg. - Borders:
border/default,border/subtle,border/hover,border/strong,border/input,border/ring,border/ink,border/control. - Intents (each has
default,hover,fg,subtle,border,text):primary,accent,secondary,success,warning,destructive,info. Primary also hasprimary/containerandprimary/on-container. - Actions and state:
action/primary-bg,action/primary-fg,action/primary-border,state/disabled-container,state/disabled-fg. - Depth:
elevation/ambient,elevation/key,effect/shadow;bevel/{highlight,lip,edge,inset,solid-highlight,solid-lip,glow};material/{fill,fill-strong,stroke,track}.
- Language accents (only meaningful in their theme, but always defined):
aurora/{1..4},block/{yellow,blue,green,pink,purple,ink-text},frame/ink,control/ink.
2.6 Variants and states are data attributes
Modes, variants and states are data attributes (data-variant, data-size and data-state), not
BEM modifier classes (epic Decision 4).
A style restyles every variant and may change a component's inner markup. It never changes the
contract: the same props, parts and data-* values exist in every style (#2416). A style that needs
a new variant value, such as Studio's card variants or Bloom's notched widget, adds it to the
component spec first, so every style can render it.