Design tokens
Tokens are the shared foundation for every visual decision in the system — colour, spacing, radius, and elevation today, with typography and motion still to be tokenized. This page documents the system architecture, naming rules, categories, and how to consume tokens in code.
Design tokens are name and value pairings that represent small, repeatable design
decisions. Instead of a component hard-coding background: #bace42, it
references var(--orbit-color-brand-fill-normal) — a token that carries both
the value and its intended meaning. When the value changes, every component using that
token updates automatically. When someone reads the token name in code, they understand
what it's for.
Source of truth: All tokens are defined as Figma Variables in the
relevant Foundations library files, exported to JSON in this repo's src/tokens/
(see manifest.json for which Figma file feeds which collection), then built
into CSS custom properties and published as the @open-point-design/orbit-theme
npm package. Never edit the exported JSON or generated CSS files directly — edit the token
source, then regenerate.
Why tokens?
System-wide updates
Change one token value and the update propagates instantly across every component and every surface that references it. No hunting for hardcoded hex values scattered across a codebase.
Theming across products
A theme is a collection of token values designed to achieve a particular look. Open Point and Social Point can share the same semantic tokens while resolving to different primitive values — one token system, two visual identities.
Design–developer alignment
When Figma Variables and CSS custom properties share the same token names, there is no translation layer. Designers and engineers refer to the same named decision, reducing handoff errors and ambiguity.
User customisation
Tokens make it practical to support different modes — light, dark, high contrast, compact — because a mode is just a different collection of values for the same token names, so no component code needs to change when one ships. Dark mode itself is on the roadmap and still being tweaked — Colors.Dark.tokens.json exists but isn't wired into the live theme yet.
Principles
Token-first implementation
Every visual property in a component must reference a token — never a hardcoded value. Hardcoded colours, sizes, or spacing values are always wrong, regardless of whether a token exists for them yet.
Semantic over descriptive
Component and semantic tokens describe intent, not appearance. --orbit-color-brand-fill-normal is correct. A raw hex value baked into a component is not — it describes what the value looks like today, not what it means or how it should be used.
Tiered structure
Tokens are organised across three tiers, in a single chain — primitive values feed Semantic (the meaning layer), and Semantic feeds Component (Web Awesome's own native naming). Code always references component or semantic tokens — never primitives directly.
Suite-wide coverage
Tokens are defined and documented with coverage across Open Point, Social Point, and Converlens. Where values differ between products today, the goal is convergence — same token names resolving to the same values across all products.
Three-tier structure
Every token belongs to one of three tiers, and they form a single chain: Primitive feeds
Semantic, Semantic feeds Component. Semantic is the meaning layer — it
holds the actual resolved values. Component mirrors Semantic in Web
Awesome's own native naming grammar, purely so WA's variant= theming works —
it doesn't introduce new values, it re-exposes Semantic's under names WA recognises.
Raw scale values, JSON/Figma only
Named by ramp and step, not by intent. Never exposed as a CSS custom property at all — these live only in Primitives.Value.tokens.json and are resolved directly into Semantic token values at build time.
color.green.60 (Primitives.Value.tokens.json) The meaning layer — holds the real values
Orbit's own namespace, exported as a verbatim mirror of every Semantic value — nothing is held back for the Component tier only. The Brand collection here is the one place product theming happens — Open Point and Social Point remap the same semantic refs to a different primitive hue. This is the tier you reference directly in your own code.
--orbit-color-text-normal --orbit-spacing-layout-stack-md Drives variant= theming automatically
Web Awesome's own token namespace — Orbit populates the values by aliasing Semantic, but the naming isn't Orbit's to change. Exists solely so variant=/appearance= can theme WA components automatically — don't reference it directly in your own CSS, use the equivalent Semantic token instead.
--wa-color-brand-fill-normal --wa-color-success-on-loud Reference the Semantic (--orbit-*) token directly in your own code — it mirrors every Component value, so it covers every role.
The one exception: styling a WA component. Use variant=/appearance= instead — it reads Component tokens automatically, no manual reference needed.
Never reference a Primitive token directly — there's no CSS custom property for one anyway; it only exists in the JSON source.
Hardcoded hex, px, or shadow values are not permitted where a token already covers the role.
Using tokens in code
Tokens reach your app as CSS custom properties, published inside the
@open-point-design/orbit-theme npm package alongside the Web Awesome component
registrations. Install it, import it, then reference the properties either through a
component's variant=/appearance= attributes or directly in your
own CSS.
1. Configure the registry (once per project)
# .npmrc
@open-point-design:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN} NODE_AUTH_TOKEN is a classic GitHub PAT with read:packages scope
on the Open-Point-Design org — set it as an environment variable locally and in your
deploy target. It authenticates npm install against GitHub Packages; it isn't
committed anywhere.
2. Install and import
npm install @open-point-design/orbit-theme import '@open-point-design/orbit-theme'; // registers every WA component used
import '@open-point-design/orbit-theme/theme.css'; // sets --wa-color-* and --orbit-* custom properties 3. Consume — Semantic custom property (most cases), or WA component attribute
.custom-panel {
background: var(--orbit-color-surface-raised);
border: 1px solid var(--orbit-color-border-normal);
border-radius: var(--orbit-radius-container);
}
<wa-button variant="brand" appearance="filled">Save</wa-button>
Reference the Semantic (--orbit-*) token directly in your own code — a
custom element, plain HTML/CSS, any non-WA surface. It's a verbatim mirror of every
Component value, so there's no role you'd need to hand-reference --wa-color-*
for instead. The one exception is variant=/appearance= on a WA
component itself — that's the only place you touch a --wa-* token, and it
reads the Component tier automatically, so brand switching needs no extra work from you.
A small number of extensions — data-color="info" on badges/callouts,
data-style="inverse" on buttons — exist for the handful of cases even
variant= doesn't reach; check component-overrides.css in orbit
if a colour or style you need isn't showing up.
Themes
A theme is a collection of token values designed to achieve a particular
look or style. Themes work by swapping the values that semantic and component tokens
resolve to — the token names stay the same, but the primitives they reference change.
This is why code must always reference a semantic or component token: code that uses
var(--orbit-color-surface-default) automatically adapts to any theme without
any code changes. Code that hard-codes #fdfcfb does not.
In the Orbit context, themes are relevant in two ways:
Open Point and Social Point
The switch happens in one place: the Semantic layer's Brand collection (Brand.Open Point.tokens.json / Brand.Social Point.tokens.json). Both files remap the same semantic refs to a different primitive hue — green for Open Point, teal for Social Point. Everything downstream (Semantic's brand roles, and every Component token mirroring them) just inherits whichever hue that product's Brand file resolved. In the published theme CSS this shows up as a [data-theme="social-point"] attribute selector layered over the :root (Open Point) defaults — that's the build output, not where the decision is made. Switching products is a token value swap at the Brand collection, not a component rewrite.
Mode and density themes
High-contrast, compact, and cosy density variants are all themes — different collections of token values for the same names. Support for additional modes can be added without touching component code; only new token values are needed.
Token categories
| Category | Prefix | Example tokens | Reference |
|---|---|---|---|
| Colour | --orbit-color-* / --wa-color-* | --orbit-color-text-normal--orbit-color-brand-fill-normal--wa-color-danger-fill-normal | Colour tokens → |
| Spacing | --orbit-spacing-* | --orbit-spacing-layout-stack-md--orbit-spacing-component-padding-inline-md | Spacing tokens → |
| Radius | --orbit-radius-* / --wa-border-radius-* | --orbit-radius-container--wa-border-radius-s--wa-border-radius-m | Radius tokens → |
| Elevation | --orbit-elevation-* | --orbit-elevation-elevation-subtle--orbit-elevation-elevation-high | No dedicated page yet |
| Typography | — | heading-xlbody-mdlabel-sm | Typography tokens → In development — documented as a visual type scale, not yet exposed as CSS custom properties consumers can reference. |
Naming conventions
Token names are kebab-case CSS custom properties, and which prefix a token uses tells you which layer it belongs to and how it's meant to be consumed:
color.{ramp}.{step} — JSON only, no CSS property --orbit-{category}-{role} --wa-color-{group}-{role} Primitive → Semantic → Component, in that order. --orbit-* is the meaning layer, Orbit's own namespace. --wa-* mirrors Semantic under Web Awesome's own native naming — Orbit populates it, but doesn't own the naming.
| Segment | Description | Examples |
|---|---|---|
color.{ramp}.{step} | Primitive colour — raw ramp value, WA-numbered 05 (darkest) → 95 (lightest). JSON-only, no CSS custom property. | color.green.60color.neutral.10 (Primitives.Value.tokens.json) |
--orbit-color-{group}-{role} | Semantic colour — the meaning layer, holds the real resolved values. A verbatim mirror of Semantic.Value.tokens.json, key-for-key. This is the token you reference directly. | --orbit-color-text-normal--orbit-color-state-hover |
--orbit-{category}-{role} | Non-colour semantic tokens — spacing, radius, elevation. Reference directly, same as colour. | --orbit-spacing-layout-stack-md--orbit-elevation-elevation-low |
--wa-color-{group}-{role} | Component colour — mirrors the same Semantic values under WA-native naming. Only ever touched via variant=/appearance=, never referenced by hand. | --wa-color-brand-fill-normal--wa-color-success-on-loud |
Rules
Semantic and component tokens are role-based, never descriptive.
Name for intent, not appearance. --orbit-color-brand-fill-normal describes the role. A raw hex value baked into a component describes what it looks like today — which may change.
Primitive tokens are descriptive by design.
Primitives describe raw values by ramp and step — color.green.60, color.neutral.10 in Primitives.Value.tokens.json. This is intentional. They're JSON-only, resolved directly into Semantic token values; there's no CSS custom property for a primitive to reference in the first place.
Reference a Semantic (--orbit-*) token in your own code — it's a verbatim mirror of Component, so it covers every role.
The only exception is a WA component itself: touch a component (--wa-*) token via variant=/appearance= only, never reference it directly in your own CSS. It exists purely so WA components can theme themselves.
Use kebab-case throughout.
No camelCase, PascalCase, or underscores anywhere in a token name.
Never hardcode a hex value, px value, or shadow that a token already covers.
If you're tempted to write #bace42 or 12px directly, check the colour, spacing, and radius foundation pages first — there's almost always a token for it.
Don't alias --orbit-radius-* for anything that renders live.
It's kept as a legacy reference only. --wa-border-radius-s/m/l is what actually drives WA component corner-rounding today.
Figma Variables structure
In Figma, tokens are organised within Variable collections using / as a
group separator, mirroring the token name structure. This ensures the Variables panel is
navigable and that token names in code trace back to the Figma Variable path they were
exported from.
Figma Variable path structure
Color / Brand / Fill / Normal
Color / Brand / On / Loud
Color / Surface / Default
Color / Status / Success / Default
Radius / SM
Spacing / Component / Padding / Inline / MD These are live, published tokens today — not a future phase. The exact set for each category is documented on the per-category foundation pages linked in the Token categories table above, and searchable in full on the All tokens page.
Was this page helpful?