Skip to content

Brand tokens — consume, don't extract

Part of the design-brief package (issue #263). This file tells a design AI where the NanaWall brand values come from, which names it may use, and what it must never do (invent brand values).

Source of truth

Brand tokens live in @nanawallweb/design-tokens (GitHub Packages; sibling repo nanawallweb/nanawall-design-tokens, reference site tokens.nanawall.com). Values are extracted from production www.nanawall.com computed styles — the package is the one place brand values are defined. NanaSelect already depends on it (package.json@nanawallweb/design-tokens ^1.0.0).

Rules for a design AI:

  1. Never invent a brand value. Colors, font stacks, type sizes, weights, and line-heights come from the package artifacts attached to this brief.
  2. Never re-extract from nanawall.com. The extraction pipeline upstream owns that; a design that "eyedroppers" the website reintroduces the drift the package exists to eliminate.
  3. Gaps go upstream, not inline. If the design needs a token the package lacks, the value is defined app-side and the gap is filed on nanawallweb/nanawall-design-tokens (see the audit below — the known gaps are already filed). A generated design should flag needed-but-missing tokens explicitly rather than silently hardcoding.

Attached artifacts

FileWhat it is
assets/tokens.jsonThe full 3-tier token tree (core → semantic → context) from dist/tokens.json v1.0.0 — every extracted brand value: color scales (teal, gray, red, green), typography, spacing, borders, shadows, breakpoints, animation, plus per-component context tokens (button, card, form, navigation, modal, panel, video, layout).
assets/contract-names.jsonThe canonical 21-name flat consumer contract snapshot.

The flat --nw-* consumer contract (21 names)

The package's stable public API — the names apps may reference directly (@import '@nanawallweb/design-tokens/contract'). Changing or removing a name is a breaking change; additions are minor. Full decision record: docs/token-delivery-contract.md in the tokens repo.

GroupNamesValues
Colors (5)--nw-color-bg-primary, --nw-color-primary, --nw-color-primary-active, --nw-color-text-heading, --nw-color-text-primarywhite; teal-300; teal-500; gray-14; gray-12
Font families (2)--nw-font-sans, --nw-font-serif'Source Sans Pro', sans-serif; 'Source Serif Pro', serif
Font sizes (7)--nw-font-size-base--nw-font-size-5xl19 / 21 / 22 / 24 / 26 / 28 / 31 px
Font weights (3)--nw-font-weight-light/regular/semibold300 / 400 / 600
Line heights (3)--nw-line-height-relaxed/tight/tighter29px / 1.2 / 36px
Transition (1)--nw-transition-fast0.15s ease-in-out

Self-hosted weights (app-side). static/fonts/ ships Sans 300/400/600/700 and Serif 400 + 600 (the display weight, added by #829 so hero/section headings render a real semibold instead of faux-bold; latin subset from @fontsource/source-serif-pro@5.2.5). All font-display: swap; the two body weights and the serif display weight are preloaded — no font CDN at runtime.

Publishing caveat: published v1.0.0 predates the contract export — the flat contract isn't on GitHub Packages yet (upstream #162). Until it ships, the nanawall theme aliases the semantic tier (--nw-semantic-*) instead. Design output is unaffected: designs target the app's --ns-* contract (next section) either way.

Delivery mechanism: the NanaSelect theme contract (--ns-*)

Generated designs must be expressed as themeable tokens, not hardcoded values. The app's theme system (docs/reference/theming.md, issue #264) defines a 50-name --ns-* contract (THEME_CONTRACT in src/lib/themes/index.ts); a design is delivered as one theme CSS file assigning all 50 names, plus component markup that references only var(--ns-*) (or the Tailwind utilities mapped from them). Two build gates enforce this: a spec fails any theme missing a contract token, and a source-wide check rejects hex/rgb/font/radius literals outside src/lib/themes/.

The 50 names, by group:

  • Surfaces: bg, surface, surface-muted, surface-inset, surface-inverse, surface-inverse-hover
  • Text: text, text-muted, text-subtle, text-faint, text-heading, text-inverse
  • Borders: border, border-subtle
  • Accent: accent, accent-hover, accent-strong, accent-soft, on-accent
  • States: danger, danger-soft, danger-border, success, success-soft, success-border, warning, warning-soft, warning-border
  • Misc color: focus, overlay, on-overlay, scrim
  • Fonts: font-sans, font-serif, font-mono, font-size-base, font-display, font-display-weight, font-numeric
  • Radii: radius-sm, radius-md, radius-lg, radius-full
  • Shadows: shadow-sm, shadow-md, shadow-lg
  • Motion: motion-duration, motion-ease
  • Imagery: media-aspect, media-frame-pad

(All color names carry the --ns-color- prefix; fonts/radii/shadows/motion/media the --ns- prefix.)

Gap audit — NanaSelect needs vs. the 21-name contract

Audited 2026-07-02 against the app's real design needs (stepper, right-sizing nudge, coverage warning, comparison table, admin data tables). Three findings:

(a) Covered. Base brand identity aliases cleanly: page background, body and heading text, brand accent + active state, both font families, the type scale, weights, line-heights, and the house transition idiom.

(b) In the 3-tier tree but missing from the flat contract — additive contract candidates; values exist upstream, they just aren't public API:

NeedWhere it lives in tokens.json
Neutral surface scale (cards, insets, muted panels)color.background.secondary/tertiary/dark/darkest, color.gray.*
Extended text roles (muted, subtle, inverse)color.text.secondary/muted/inverted*
Border colorscolor.border.*
Error/dangercolor.feedback.error (#c8102e), color.red.*
Success-ish greencolor.green.*, color.green-accent.*
Radiiborder.radius.sm/md/full (11px / 13px / 50%)
Shadowsshadow.sm/md/drop
Focus treatmentcolor.interactive.*

(c) Absent from the package entirely — no extracted value exists because production nanawall.com has no equivalent surface:

NeedWhy NanaSelect needs it
Warning trio (warning, -soft, -border)Coverage warnings on results; partial-data states in compare
Success soft/border variantsRight-sizing nudge — an affirmative call-out card needs a soft fill + border, not just a green
Mono font stackAdmin data tables, audit log, technical values
Stepper tokens (progress track/fill, step states)The guided flow's signature component
Comparison-table tokens (sticky header, row hover, column highlight)/compare and admin tables

Category (c) values are currently defined app-side in the theme files (per the theming doc's rule: defined locally, filed upstream, never silently forked). The audit is filed upstream as nanawall-design-tokens#163 (contract additions from category (b) + new authored tokens from category (c)); the unpublished contract itself is #162.

Addendum (2026-07) — public-experience direction proposals

The 2026-07 direction (§§3, 7) adds five proposed tokens to category (c) — no extracted value exists upstream for any of them:

NeedWhy NanaSelect needs it
--ns-color-scrim (theme token)Legibility scrim over full-bleed photography; art direction bans duotones/heavy grading, so themes differentiate imagery via crop, scrim weight, and frame (Graphite deeper, Linen softer)
--ns-motion-duration / --ns-motion-ease (theme tokens)Motion personality per theme; prefers-reduced-motion zeroes them globally
--ns-layout-hero-bleed / --ns-layout-band-bleed (layout tokens)Full-bleed band vocabulary (hero, photo strips, CTA bands). Bleed is a layout property, not a theme property — these live in the layout files (Editorial on, Spotlight imagery-only, Standard contained), with the scrim that keeps text legible over the bled image staying a theme token: clean seam, no coupling

Per the process above the values are defined app-side and the proposals are filed upstream as nanawall-design-tokens#164 (extends the #163 audit).

Shipped app-side (#843, 2026-07-06): --ns-color-scrim and --ns-motion-duration / --ns-motion-ease are now in the contract and all four themes, with per-theme AA-measured scrim values and motion personalities; prefers-reduced-motion zeroes durations globally. The same change added three type-register tokens to category (c) — --ns-font-display, --ns-font-display-weight, --ns-font-numeric (display voice per theme; numeric/spec register, mono in Graphite) — appended to the #164 proposal. See theming.md § Per-theme voice.

Shipped app-side (#844, 2026-07-06): two imagery-treatment tokens join category (c) — --ns-media-aspect (the theme's signature crop for hero-scale imagery: 3:2 / 21:9 / 4:3) and --ns-media-frame-pad (mat whitespace around the photograph; Linen's mount). Per-theme treatment table: art-direction.md § Imagery. Also appended to #164.

Implication for a design AI: design with the --ns-* contract as your palette-shaped hole. Where an --ns-* token has a brand source (categories a and b), take the value from assets/tokens.json. Where it doesn't (category c), you may propose values — harmonized with the brand scales in tokens.json — and must list them as proposed additions in your delivery notes.