Skip to main content
NetScriptdocs Search docs ⌘K v0.0.9
Browse documentation

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.derive describes them. CSS keeps color-mix(), and Figma receives the computed value.
  • Collections and modes are not tiers. Scheme, Theme, Theme B, Base gray, Base accent, Density and Studio flavor modes are contexts the resolver chooses between (§2.2). Theme set only switches the aliases between Theme and Theme 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's accent/{l,d}/solid. Each theme's DESIGN.md records 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.md names 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-part names, data-variant, data-size and data-state values, 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:
      1. Its component sources: one per component, shipped as a registry style item (#2415).
      2. 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's DESIGN.md → NetScript Mapping. The tokens build registers every listed role before the style ships; unknown names fail CI.
      3. A complete core map: every core semantic role still resolves under the style, so token-only presets and app-owned components keep working.
    • A style restyles its own components only, never app-owned markup outside them.
  • 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 has primary/container and primary/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.

Read GUIDELINES.md

Search all documentation

Type a term to search the documentation.