The system
Noctis is a token-governed component system: one OKLCH seed generates every color, and every component reads roles, never values. Get the contracts right and the whole product re-themes from a single seed, with no rebuild.
Noctis publishes as a single package. Add it, then import primitives, the provider, and the token entry points from the one barrel.
pnpm add @stridge/noctisThe entry point
Mount NoctisProvider once near the root — it wires theme, locale, direction, and the CSP nonce into one tree, and every primitive beneath it reads from those contexts. It takes a theme seed plus themeOverrides, and locale / direction / nonce / messages for the i18n surface. Import it from the main barrel, never a subpath.
import { NoctisProvider } from "@stridge/noctis";
export function Root({ children }) {
return (
<NoctisProvider
locale="en-US"
theme={{ background: "#0f1010", accent: "#5e6ad2", contrast: 30 }}
>
{children}
</NoctisProvider>
);
}Design principles
Clarity over cleverness. Strong defaults over configuration. Tokens over values. Logical layout over physical. Two accents, not a palette — primary acts, the accent only signals.
Token architecture
Six strata, flowing from the public inputs you tune down to the engine's private ramp: seeds (--noctis-seed-*, you tune) → foundations (--noctis-{category}-*) → semantic roles (--noctis-color-{role}, what components read) → component tokens (--noctis-<name>-*, the override seam, shipped empty) → slot internals (--_<name>-*, private, generated), all resolving against the engine primitives (--noctis-engine-*, the private terminal layer). Products touch only the roles and the mints; swap the seed and everything that derives from it re-solves with no rebuild — see Tokens.
Component philosophy
Behavior and accessibility come from Base UI. Styling is precompiled, hand-authored, framework-neutral CSS in @layer noctis.components, keyed off each element's data-slot and reading the generated --_<name>-* internals and declared --noctis-* roles — no Tailwind in component styling, no tv(), no .styles.ts. Variants are typed: the unions live in a Tabs-shape namespace, e.g. Button.Variant. No forwardRef, logical utilities only, a data-slot on every part.
Theming model
The engine APCA-solves a legible token set from the seed — background, accent, contrast — with contrast calibrated at 30 and clamped to [0, 100]. The same roles then re-derive across four elevation scopes (sunken, root, elevated, menu) by re-running the engine at a shifted canvas, so a panel and the page it sits on read the same role names yet land at different lightness — see the Theme engine and Layers.
Customization
One override ladder, coarse to fine: seeds (the System Controls) → generation-time overrides (re-derive the whole graph) → cascade overrides (set one var, no re-derive) → component mints (shipped empty, waiting for you) — see Customization.
Color
Two accents, not a palette — primary acts, the accent only signals. Every fill, border, and text role is derived from the seed and guaranteed legible against its scope, never hand-authored — see Color.
Internationalization
Locale and direction resolve on the server for a correct first paint. RTL is a layout contract — logical properties, mirrored directional glyphs, locale-aware formatting — see Internationalization.
Motion model
Motion is dampened and purposeful, driven by motion tokens, and it respects reduced-motion at every level — including a system-wide motion control.
Accessibility model
Correct semantics, full keyboard operability, visible focus, and APCA-guaranteed contrast. Every primitive passes axe with zero serious violations, in LTR and RTL.
System maturity
45 primitives ship today, every one against the same contracts — the tokens, the OKLCH theme engine, and the layering model are stable, so each new piece inherits the system rather than reinventing it. Everything ships from this repo and is reviewed against the principles, the token contract, and the a11y bar.