@orvain/tokens
v0.1.3
Published
Orvain Design System — design tokens (CSS custom properties, JS/TS, SCSS)
Downloads
0
Maintainers
Readme
@orvain/tokens
The design tokens behind the Orvain Design System, compiled with Style Dictionary into CSS custom properties, SCSS variables and JS/TS constants.
Commercial software. See LICENSE — use is governed by your agreement with Orvain Labs.
Install
npm install @orvain/tokensUsage
CSS custom properties (what the components use)
import '@orvain/tokens/css';Import it once, before your component styles. It defines the --orvain-*
properties on :root and carries
the one global rule the components rely on: under prefers-reduced-motion: reduce
every transition and animation is cut to 0.01ms (WCAG 2.3.3). It lives here, not
in @orvain/react / @orvain/vue, so it applies however the components are
imported.
Typeface
For the intended look, add Satoshi from Fontshare (free). The components use the Satoshi
typeface and fall back to system-ui. The package does not include Satoshi: its licence
does not allow handing the font file to others, so each project loads it from Fontshare.
Paste this once in your HTML head:
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=satoshi@300,400,500,700,900&display=swap" />Check the font's licence at fontshare.com for your use. Without it, everything works and renders in the system font.
There are two stylesheets. Both are minified of comments, and both carry the same dark-mode blocks (see below) and the reduced-motion rule:
| Entry point | File | Contents | Size (gzip) |
|---|---|---|---|
| @orvain/tokens/css | dist/tokens.css | Every foundation token (colour, typography and the type scale, radius, border width, sizing, shadow, motion, breakpoints, chart palette, focus rings) plus every component token the Orvain components read. | ~30 KB |
| @orvain/tokens/css/full | dist/tokens.full.css | Every token, including the component tokens no component reads yet. Use it instead of @orvain/tokens/css if you build your own components on them. | ~66 KB |
.my-banner {
background: var(--orvain-color-brand-surface-clear);
color: var(--orvain-color-brand-content-default);
border: 1px solid var(--orvain-color-brand-border-subtle);
}Units
Lengths carry their unit: --orvain-sizing-xxxl: 128px, --orvain-breakpoint-tablet-min: 768px,
--orvain-type-scale-desktop-h1-font-size: 62px, and most component geometry tokens. Use
them directly (width: var(--orvain-sizing-xxxl)).
Tokens that components still read as calc(var(--orvain-x) * 1px) stay bare numbers
until those components move to the unit-bearing form: the --orvain-border-radius-* and
--orvain-border-width-* scales, --orvain-typography-font-size-*, and about 200
component tokens (--orvain-badge-geometry-lg-height: 32). Adding the unit while that
* 1px exists would make the property invalid; the token check
(node packages/tokens/scripts/usage.mjs) fails on exactly that. Weights, opacities,
scale factors and line-height ratios are unitless by design.
SCSS
@use '@orvain/tokens/scss' as orvain;JavaScript / TypeScript
import { colorBrandPrimary600, colorContentMuted } from '@orvain/tokens';Token names are camelCased from their source path, so
color.brand.primary.600 becomes colorBrandPrimary600. Use this entry point
for canvas rendering, chart libraries and anywhere else CSS custom properties
can't reach — prefer the CSS layer everywhere else, because it is what theming
overrides act on.
Responsive tokens (breakpoints, type scale)
Figma "Responsive Tokens" are exported per mode: breakpoint.{mobile,tablet,desktop}.{min,max}
(320–767 / 768–1023 / ≥ 1024 px) and typeScale.{mobile,tablet,desktop}.<style>.* (font size in
px; weight, line-height ratio and letter-spacing as a percentage are unitless).
On top of those, tokens.css defines a viewport-aware alias per type style that switches
at the Figma breakpoints, mobile-first:
.title {
font-size: var(--orvain-type-responsive-h2-font-size); /* 32 → 40 → 48px */
line-height: var(--orvain-type-responsive-h2-line-height);
font-weight: var(--orvain-type-responsive-h2-font-weight);
letter-spacing: var(--orvain-type-responsive-h2-letter-spacing); /* Figma % → em */
}Custom properties can't be used inside @media; write the literal and name the token:
@media (min-width: 768px) { /* breakpoint.tablet.min */ … }. JS code can use
BREAKPOINTS / MEDIA from @orvain/utils.
Theming
Redefine any property on :root.
Rebranding
--orvain-color-brand-primary-* is the one brand ramp: every semantic brand
token (--orvain-color-brand-content-default, …-surface-clear,
…-border-focused, …) and every component token with a brand colour aliases a
step of it. Override the whole ramp to rebrand the UI:
:root {
--orvain-color-brand-primary-50: #f5f3ff;
--orvain-color-brand-primary-75: #f1edff;
--orvain-color-brand-primary-100: #ede9fe;
/* … 150, 175, 200, 250, 300, 350, 400, 450, 500 … */
--orvain-color-brand-primary-600: #7c3aed; /* brand fill, links, selected states */
--orvain-color-brand-primary-700: #6d28d9; /* hover */
/* … 800, 900, 950 */
}| Step | Default | Used for |
|---|---|---|
| 50, 75, 100 | #f0f6ff, #ebf2ff, #dae8ff | brand tints, selected and hover surfaces |
| 150, 175, 200, 250 | #c9dcff, #bcd3ff, #b3d0ff, #a6c5ff | soft surfaces, tracks |
| 300, 350, 400, 450 | #87b3ff, #629dff, #4d91ff, #3582ff | soft borders, lighter brand fills; 350 is brand text in dark mode |
| 500 | #1a6ef8 | focus ring (--orvain-color-brand-border-focused) |
| 600 | #005cf8 | brand fill, brand text and links |
| 700, 800, 900, 950 | #0042b7, #003395, #002873, #000f2d | hover, pressed, deep surfaces |
Steps 75, 150, 175, 250, 350, 450 and 950 are Figma's Primitives/Blue values that sit
between the code ramp's steps. The focus ring follows the brand (step 500); keep it
at 3:1 against white, or pin it on its own with --orvain-color-brand-border-focused.
--orvain-color-primitive-blue-* is the blue hue ramp (chart series, colour swatches)
and does not change with the brand. A few dark-mode brand tints are rgba() of the
default blue and do not follow an override yet.
Override the ramp on :root. A custom property's var() resolves where it is
declared, and the aliases are declared on :root, so a ramp override on a
subtree only reaches the few places that read a ramp step directly.
Dark mode (opt-in)
Pages render light by default: importing @orvain/tokens/css never turns a page
dark on its own, whatever the visitor's OS setting. Ask for dark with a
data-theme attribute on <html>, or on any element to theme just that subtree:
| data-theme | Result |
|---|---|
| none, or light | Light tokens. light inside a dark subtree makes a light island. |
| dark | Dark tokens. |
| system | Follows the OS (prefers-color-scheme): dark when the OS is dark, light otherwise. Set it on <html>. |
<html data-theme="system">Each value also sets color-scheme, so scrollbars and native form controls match.
The dark values are still under design review: 18 of the 277 come from Figma's
Dark mode, the other 259 are derived and may change before 1.0.
Dark values live in themes/dark.json; build.mjs compiles them into the
[data-theme="dark"], [data-theme="system"] and [data-theme="light"] blocks at the
end of both stylesheets.
Z-index
One stacking scale, in both stylesheets. Component CSS uses only these (a
literal z-index fails the token usage check); use them for your own layers
too.
| Token | Value | For |
|---|---|---|
| --orvain-z-index-base | 0 | the resting layer; local order inside a component is calc() on base / raised |
| --orvain-z-index-raised | 1 | one step above siblings inside a component (a selected segment, a slider thumb) |
| --orvain-z-index-dropdown | 1000 | menus, listboxes and popovers, only where the Popover API is missing |
| --orvain-z-index-sticky | 1100 | sticky and fixed page chrome: app bars, mobile tab bars, save bars |
| --orvain-z-index-overlay | 1200 | full-screen backdrops |
| --orvain-z-index-modal | 1300 | modals, drawers and tours above their backdrop |
| --orvain-z-index-popover | 1400 | popovers that can open from inside a modal (Popover family, context menus) |
| --orvain-z-index-toast | 1500 | toast and notification viewports |
| --orvain-z-index-tooltip | 1600 | tooltips and hover cards |
Floating panels and <dialog>-based overlays (modals, drawers) normally render
in the browser's top layer, above every z-index; the scale orders everything
else, and the floating panels where the Popover API is missing. See Overlays
and layering in the React or
Vue README.
Contrast
Colour tokens whose name ends in content are verified against WCAG 2.2 AA
(4.5:1) on --orvain-color-neutral-0, and --orvain-color-brand-border-focused
against SC 1.4.11 (3:1). Several sit between 4.2:1 and 4.5:1 on
--orvain-color-neutral-100, so avoid body text on surfaces darker than that.
--orvain-color-content-disabled is deliberately below the threshold; the
success criteria exempt inactive controls.
Status text has two strengths. --orvain-color-{error,success,warning}-content-default
passes on white and on that status's clear tint; …-content-on-tint also passes on
the subtle tint (tinted badges, button hover and pressed tints, stat pills). Labels on
solid status fills: white on --orvain-color-{error,success}-surface-solid, and
--orvain-color-content-on-bright on the bright success and warning fills, where
white fails. The focus-ring tokens (…-border-focused) are for rings only, not text.
Building
Tokens are authored as JSON under src/ (dark values in themes/dark.json) and
compiled by build.mjs:
pnpm --filter @orvain/tokens buildBesides the two published stylesheets, the build writes
dist/tokens.annotated.css: every token with the Figma provenance note from its
description. It is for authoring and is not published.
usage.json lists the component tokens that tokens.css ships on top of the
foundation groups (foundation.mjs). It is generated from the code: after a
component starts or stops reading a component token, run
pnpm --filter @orvain/tokens usage:updatepnpm check runs node packages/tokens/scripts/usage.mjs, which fails when
usage.json is out of date, when code references a token that is only in
tokens.full.css, or when code applies a unit to a token that already has one.
Related packages
@orvain/react,@orvain/vue— the component libraries@orvain/mcp-server— serves these tokens to MCP clients
