@stridge/noctis-design-tokens
v1.0.0-beta.32
Published
 
Readme
@stridge/noctis-design-tokens
The semantic token layer on top of @stridge/noctis-theme-engine. The engine generates the private
primitive ramp; this package maps it to intent-named semantic roles as framework-neutral CSS
variables (--noctis-color-*), ships the foundation scales, and wires the elevation scopes.
The Tailwind bridge is one optional consumer of those roles. Components style with the roles below
— never the primitives.
Usage
The token CSS stands alone — it is @imported by @stridge/noctis/styles.css, and needs no
styling framework. Import it directly only when consuming this package standalone:
// app CSS entry
@import "@stridge/noctis-design-tokens/tokens.css";Tailwind authors who want the roles as utilities additionally import the bridge stylesheet
(@stridge/noctis-design-tokens/tokens.tailwind.css).
// app root — keeps the root theme + elevation scopes in sync across theme changes
import { ThemeProvider } from "@stridge/noctis-design-tokens/react";
<ThemeProvider>{children}</ThemeProvider>;SEMANTIC_ROLES and ELEVATION_SCOPES export the catalog as data (for the debug swatch
page); defaultThemeCss() returns the build-time default :root CSS; applyThemeWithScopes
applies a theme + both scopes to an element.
Token grammar
Every token serializes to a canonical CSS custom property under one closed grammar. The
{category} segment is drawn from the structural-category vocabulary, never written into the
id; cssVarName composes the namespace, tier, and category segments around the serialized
name body.
| Form | Stratum | Shape |
| --- | --- | --- |
| Foundation | foundation | --noctis-{category}-{name} |
| Semantic | semantic | --noctis-{category}-{name}, and for color roles specifically --noctis-color-{role} |
| Seed | seed | --noctis-seed-{name} |
| Component mint | component | --noctis-{component}(-{anatomy})-{property}(-{state}) |
| Slot internal | (recipe) | --_{component}-* |
Foundation and semantic tokens carry a structural category; seeds and component mints do not.
Semantic tokens are always category color, so a semantic role serializes as
--noctis-color-{role}. A component mint omits any segment it lacks, in fixed order
(component → anatomy → property → state); the rest (no-state) form drops the trailing
state segment. Slot internals live in the --_ namespace a recipe declares on its slot to
carry the resolution chain.
The 18 structural categories (TOKEN_CATEGORIES):
color · space · size · text · leading · radius · shadow · duration · ease ·
z · font · tracking · breakpoint · container · border · opacity · blur ·
animate.
Namespace contract
Every canonical token serializes under the --noctis prefix (ROLE_VAR_PREFIX).
--noctis-engine-* is the engine's reserved private namespace. The graph validator
rejects any graph token whose canonical name serializes into it — the engine owns that
namespace for its primitive ramp, and no foundation, semantic, seed, or component token may
collide with it.
Canonical names are consumed through the bridge: via the Tailwind bridge utilities the
generator emits (bg-*, text-*, border-*, the named shadow-*, the scale keys), or, in a
recipe, via a minted --_ internal. A recipe never references the canonical literal directly —
that boundary is enforced by stridge/banned-namespaces.
Role catalog
Each role is a Tailwind utility backed by one engine primitive. The default Tailwind palette is disabled, so these are the only colors available.
Surfaces
| Utility | Role |
| --- | --- |
| bg-background | App canvas |
| bg-hover | Hover on a canvas row |
| bg-surface / bg-surface-hover | Card / panel + its hover |
| bg-surface-raised | Highest neutral surface |
| bg-sunken | Recessed wells |
| bg-selected / bg-selected-hover | Selected row + its hover |
| bg-focus | Keyboard-focused row |
| bg-overlay | Dialog scrim |
| bg-sidebar-item / bg-sidebar-item-active | Sidebar item hover / active |
Text
text-foreground (primary) · text-muted (secondary) · text-subtle (faint) · text-link.
Borders & ring
border-faint · border-border (default) · border-strong · border-selected ·
border-field · bg-divider / border-divider · ring-ring.
Primary
The neutral (white) emphasis, decoupled from the chromatic accent: bg-primary ·
text-primary-foreground · bg-primary-hover / bg-primary-active (the white dimmed toward the
seed — real opaque tokens that re-derive per elevation, never a translucent white).
Accent
bg-accent · text-accent-foreground · bg-accent-hover · bg-accent-active ·
bg-accent-muted.
Controls & fields
Controls are a derived neutral tier off the seed (their own gentle gain), so each step is a solid fill — not an opacity wash — and re-derives inside every elevation scope:
bg-control + text-control-foreground / bg-control-hover / bg-control-selected /
bg-control-selected-hover (secondary buttons, segmented) · bg-control-ghost /
bg-control-ghost-hover / bg-control-ghost-selected / bg-control-ghost-selected-hover (ghost
buttons, tabs, menu items) · bg-field / bg-field-hover / bg-field-focus + border-field
(inputs). Filled chromatic controls take a desaturated on-fill label (text-accent-foreground,
text-danger-foreground); disabled controls dim with opacity-disabled, never a color swap.
Status
For danger / success / warning / info: bg-* (fill), text-*-foreground (on-fill),
bg-*-muted (soft surface), text-*-muted-foreground (on-muted).
Shadows & type
shadow-card / shadow-popover / shadow-modal / shadow-inset; the named size scale
text-micro … text-title-1 (all scaled live by the root --noctis-seed-font-scale multiplier); families
font-sans / font-mono; the radius scale rounded-xs … rounded-xl (min()-capped surfaces) plus
rounded-control (buttons/chips — follows the --noctis-seed-radius knob uncapped, so it pills at the Pill
setting) and rounded-full (circles); ease-standard|in|out|in-out.
Elevation scopes
A scope is a full re-generation of the theme at a shifted base lightness — text re-solves its
contrast and borders re-derive — so a raised surface is a genuinely different, internally
consistent color, not a tint. Set a data-elevation attribute; everything inside keeps using
the normal roles and resolves to that scope:
data-elevation="elevated"— dialogs, sheets, drawers, popovers, tooltips.data-elevation="menu"— the highest float: dropdown and context menus, select listboxes, the command palette (lifts a touch more thanelevated).data-elevation="sunken"— recessed content wells and insets.
Page-level chrome (header, sidebar, rail) stays at the root level — no scope — so it sits flush with
the canvas it frames, set off by a border. Only overlays float (elevated/menu) and only wells recede (sunken).
Portals overlay to <body>, so set data-elevation on the portalled node itself
(Popover.Popup, Menu.Popup, …), not a JSX ancestor. Scopes are absolute and
single-level — computed from the active theme's base, they do not compound when nested.
Paint a control-hosting elevated surface with bg-background, not bg-surface. Controls
derive off their scope's base (bg-1), so a ghost or secondary control only separates from the
surface it sits on when that surface is the scope base. A dialog, sheet, rail, or menu therefore
uses bg-background inside its data-elevation scope (the scope's lift, border, and shadow give it
presence) — then a ghost button's hover clears the panel by the full control delta. bg-surface
(bg-3) is for non-interactive raised content (cards, wells); a ghost control placed on it would
hover almost invisibly. The invariant: an elevated surface that hosts controls paints its
elevation's own base, and controls re-derive off that same base — so surface and control can never
drift apart.
Component consumption contract
Every planned surface is expressible with roles alone:
- Card / Panel:
bg-surface border border-border shadow-card; clickable →hover:bg-surface-hover. - Input / Textarea / Select:
bg-field border border-field shadow-inset; hover →hover:bg-field-hover hover:border-field-hover; focus →bg-field-focus focus-visible:ring ring-ring border-field-focus; invalid →border-field-invalid. - Button — primary (neutral white):
bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active. - Button — accent:
bg-accent text-accent-foreground hover:bg-accent-hover active:bg-accent-active. - Button — secondary:
bg-control text-control-foreground border border-border hover:bg-control-hover; pressed →bg-control-selected. - Button — ghost / Tab / Menu item:
bg-control-ghost text-muted hover:bg-control-ghost-hover hover:text-foreground; active →bg-control-ghost-selected. - Menu / Select content: portalled
data-elevation="menu",bg-background border border-border shadow-popover; items use ghost-control roles; keyboard item →bg-focus. - Popover:
data-elevation="elevated"+bg-background border-border shadow-popover(control-hosting; a pure-content popover may usebg-surface). - Dialog / Drawer / Sheet: scrim
bg-overlay; paneldata-elevation="elevated"bg-background border-border shadow-modal. - Sidebar / Rail: page chrome at the root level —
bg-backgroundset off by a border (nodata-elevation); itemshover:bg-sidebar-itemor ghost-control roles, active →bg-sidebar-item-active text-foreground. - Table rows:
hover:bg-surface-hover; selectedbg-selected, selected+hoverbg-selected-hover, keyboardbg-focus. - Separator: the standalone hairline fills from the neutral
borderrole; heavier group dividers drawn inside a menu/list step up todivider.
Status families
Each of danger / success / warning / info exposes a complete sub-role set: bg-<s>
(solid fill) · text-<s> (colored text on neutral) · text-<s>-foreground (on-fill text) ·
bg-<s>-hover · bg-<s>-muted + text-<s>-muted-foreground (soft surface) · bg-<s>-faint
(row wash) · border-<s> (solid outline) · border-<s>-subtle (muted-callout outline). A
status-tint gradient (bg-<s>-gradient, faint hue → neutral) backs "In review" / "On
hold" status cards. Destructive button: bg-danger hover:bg-danger-hover text-danger-foreground.
Charts & avatars (static, accent-independent)
Chart and avatar palettes are authored, not engine-derived — categorical identity must
stay stable when the accent changes, so they do not re-theme. They carry light and dark
values and switch on the resolved mode (data-theme).
- Charts:
bg-chart-1…bg-chart-8(categorical series, hue- and lightness-separated for color-vision-deficiency safety) ·text-chart-positive/text-chart-negative/text-chart-neutral(= success / danger / muted) ·border-chart-grid·text-chart-axis·bg-chart-track·bg-chart-tooltip. - Avatars:
bg-avatar-1…bg-avatar-10chosen byavatarIndex(id)(a stable hash);text-avatar-foregroundis the knockout for initials.
States: disabled, inverse, fields, forms
- Disabled = opacity, not a color family. Apply
opacity-disabled(the--noctis-opacity-disabledscalar) to disabled controls.text-disabledis the one explicit disabled color, for text that must stay legible where dimming the whole element is wrong. - Inverse:
bg-inverse+text-inversefor inverted chips/snackbars; tooltips reuse the samebg-inverse+text-inverse— there is no dedicated tooltip color role. - Fields:
bg-field border border-field; state fillsbg-field-hover(rest hover) andbg-field-focus(accent-tinted focus surface); state bordersborder-field-hover,border-field-focus,border-field-invalid. - Form elements:
bg-toggle-track-{off,on}(+ hover; the on-track stays full accent when disabled) +bg-toggle-thumb;bg-checkbox+border-checkbox(+-hover) +bg-checkbox-checkedtext-checkbox-check;bg-radio-checked+border-radio.
- Chrome / utilities:
bg-header;bg-scrollbar-thumb/bg-scrollbar-track;bg-code+text-code-foreground;bg-kbd+border-kbd;text-menu-shortcut;bg-data-grid-header/bg-data-grid-row-hover(-strong)/border-data-grid/border-data-grid-selected;shadow-focus.
SEMANTIC_ROLES (engine-backed) and STATIC_TOKENS (authored) export the full catalog as
data; the docs site's /foundations/tokens page renders the full token graph — every scale,
role, and component token.
