12 KiB
@wrnexus/design-system
Browser-safe, dependency-free tokens and versioned component recipes. The engine validates data-only documents, resolves aliases and inheritance, compiles deterministic CSS, and integrates with the existing WrNexus theme pipeline. Adoption is opt-in. Core components have additive owner/part hooks; their existing props, events, slots and default styles remain available.
Apply to an existing application
import { defineConfig } from "@wrnexus/styles";
import { defineDesignSystem } from "@wrnexus/design-system";
const system = defineDesignSystem({
schemaVersion: 1,
id: "square",
name: "Square",
version: "1.0.0",
tokens: {
"control.radius": { type: "dimension", value: "0", legacy: "radius-sm" },
"panel.radius": { type: "dimension", value: "0", legacy: "radius-md" },
},
});
export default defineConfig({
theme: {
designSystem: { system },
// Explicit application tokens retain final precedence.
themes: { dark: { "radius-md": "4px" } },
},
});
The adapter writes existing --wrn-* variables. A normal Button already consumes radius-sm; an explicit pill prop still uses the existing pill recipe. Hard-coded geometry is customizable only where a versioned component recipe supports the property.
Core presets and recipes
import { SOFT_DESIGN_SYSTEM } from "@wrnexus/design-system";
export default defineConfig({
theme: { designSystem: { system: SOFT_DESIGN_SYSTEM } },
});
Other exports: SQUARE_DESIGN_SYSTEM, ANGULAR_DESIGN_SYSTEM, and CORE_DESIGN_SYSTEMS. Square uses flat, compact, outlined controls; soft uses generous rounded geometry; angular uses individual CSS rotation and hard shadows. All include light/dark token sets.
The 14 version-1 contracts cover Button, Input, Textarea, Select, Checkbox, Radio, Switch, Card, Alert, Modal, Nav, ContextMenu, PageHeader and Container. Inspect CORE_COMPONENT_CONTRACTS or componentDesignContract(name) from this package or the optional @wrnexus/ui/design-contracts subpath. Descriptors list source, family, parts, property allowlists, axes/defaults/accepted values, state selectors, slots, tokens, constraints and compatibility. COMPONENT_CONTRACTS covers all 104 components; CATALOG_COMPONENT_CONTRACTS adds the other 90 while preserving the core registry. Catalog descriptors include explicit provider/native/content ownership and region limitations. All three presets include complete catalog recipes.
const system = defineDesignSystem({
schemaVersion: 1,
id: "company",
name: "Company",
version: "1.0.0",
extends: ["soft"],
tokens: {},
recipes: {
Button: {
contractVersion: 1,
base: { control: { borderWidth: "2px" } },
variants: [
{
when: { variant: "outline", size: "sm" },
parts: { control: { borderWidth: "3px" } },
},
],
states: [{ state: "disabled", parts: { control: { opacity: 0.55 } } }],
},
},
});
// theme.designSystem = { system, registry: { soft: SOFT_DESIGN_SYSTEM } }
Recipes merge across inherited systems: base properties merge per part; variant and state arrays append in declared order. Compilation applies base, matching variant/compound rules, then state rules. Conditions use zero-specificity selectors so rule ordering is meaningful. Unsupported components, versions, parts, axes, states, properties, token types and unsafe values fail validation. Property references use the same { ref } syntax; references mapped to legacy variables retain explicit app token precedence. Rotation is a literal angle within ±8 degrees, permitted only on Button control and Card panel; it composes with existing pressed transforms. backgroundImage only accepts none to safely remove a legacy gradient.
Owner/part hooks are direct: data-wrn-component identifies the root; each part has data-wrn-owner, data-wrn-part and copies of relevant data-wrn-axis-* values. Ancestor variant selectors are not used, preventing nested-component leakage. Explicit pill, nondefault size/color, radius/shadow and field variant choices are guarded where applicable. This does not claim that arbitrary application selectors outrank a recipe; use app token overrides or deliberate selectors for property-level exceptions.
resolveThemeConfig includes an optional recipeCss field. The application SSR pipeline emits it after extracted component styles in a nonce-tagged data-wrn-design-recipes style element, for both development and production. Custom SSR consumers must pass componentRecipeCss: theme.recipeCss to renderDocument/renderDocumentStream. The theme CSS endpoint remains token-only. No recipe block is emitted without recipes.
Scoped compilation accepts a unique island identifier, e.g. scope: "billing-preview". Put data-wrn-design="billing-preview" and data-theme on the island owner; nested islands must use distinct identifiers. Recipe selectors stop at nested owners, independent of stylesheet order. Reusing the same identifier across nested islands is unsupported. Global recipes exclude explicitly scoped islands. Current Modal/ContextMenu panels stay in their owner tree and inherit the island; future portalled implementations must preserve this context explicitly. Ejected components without hooks are application-owned and do not receive recipes automatically.
For the interactive developer preview, run bun run scripts/preview-design-systems.ts from the repository root. It renders the same content through actual components and supports mode changes and linked/inline token CSS. It uses test rendering utilities and is not shipped to application clients.
Imported color tokens suppress the implicit default accent so the built-in blue accent does not overwrite the brand. Explicit theme.accent.default and user-selected accents retain their existing behavior. This does not automatically generate all hover/active/contrast colors: supply each required semantic role. Existing defaults fill unspecified roles.
Document format
Required fields: schemaVersion: 1, id, name, version, and tokens. Optional: description, extends, defaultMode, modes, densities, recipes. The published @wrnexus/design-system/schema.json is structural; runtime validation additionally enforces CSS grammar, graph semantics and component contracts. No remote schema fetch is performed.
Each flat, namespaced token has type, value, optional legacy suffix and description. Types: color, dimension, number, fontFamily, duration, shadow, fontWeight, colorScheme. An alias is exactly { "ref": "other.token" } and must reference the same type. Modes and densities contain typed token maps, not partial token objects. Overridden tokens retain inherited metadata when omitted; their type cannot change.
CSS values use a deliberately bounded subset: basic hex/named and numeric color functions; unit-bearing dimensions or string "0"; finite numbers; simple font-family lists; durations; standard numeric shadows; font weights; light/dark color schemes. Unsupported expressions such as var(), url() and calc() are rejected. Use typed aliases for references. Future value grammar expansion can be additive.
legacy explicitly maps a token to an existing variable, such as radius-sm. color-scheme requires type colorScheme and emits the native CSS property. No implicit semantic mapping is guessed. The standalone compiler emits every token as --wrn-ds-<name-with-dots-replaced-by-hyphens> plus mapped legacy variables; collisions are errors. The legacy adapter exports mapped variables only.
Engine APIs
import {
validateDesignSystem,
resolveDesignSystem,
compileDesignSystem,
adaptLegacyTheme,
} from "@wrnexus/design-system";
const options = { registry: { company: companySystem }, mode: "dark", density: "compact" };
const issues = validateDesignSystem(system, options);
const resolved = resolveDesignSystem(system, options);
const { css } = compileDesignSystem(system, options);
const preview = compileDesignSystem(system, { ...options, scope: "preview" });
const theme = adaptLegacyTheme(system, existingThemeConfig, options);
Registry IDs must match document IDs. Parents merge depth-first in declared order, then child tokens. Selected mode overrides apply across layers, followed by selected density. Aliases resolve after these changes. Explicit app theme overrides are last in the adapter. Default mode is inherited from the last applicable layer; there is no implicit density. All operations leave inputs unchanged. Resolved tokens include source and direct-reference provenance; follow the referenced token for the rest of the alias chain.
validateDesignSystem(unknown, options) returns structured diagnostics and checks all declared mode/density combinations, including output collisions. It returns the first graph/compilation failure after structural validation. assertDesignSystem and defineDesignSystem validate structure/typed values only; use full validation before saving imported drafts. Resolution validates the selected configuration. Invalid compilation throws DesignSystemError with stable codes and paths. Resource limits bound inheritance, alias depth and validation combinations.
Standalone CSS emits the selected/default configuration at :root and mode blocks on :root[data-theme]. Optional scope uses [data-wrn-design="name"] and expects the mode attribute on that same owner. Density is selected at compile time. Runtime density switching and propagation into externally portalled overlays are future work. Application integration uses the existing global theme runtime and SSR asset delivery; do not include both standalone CSS and the adapter unless you deliberately manage their precedence.
CLI
wrnexus design validate system.json
wrnexus design inspect system.json --mode=dark --density=compact
wrnexus design compile system.json --registry=systems.json
systems.json maps parent IDs to full documents. Commands read bounded JSON inputs, never execute user files, and print output to stdout; they do not write, publish or deploy. Inspection includes resolved values and provenance. Validation reports nonzero failure through the existing CLI error path.
See examples/square.json for a complete document. The engine root can be bundled for the browser without filesystem or provider imports. No studio, AI credentials or network access is required.
Validation scope
Tests cover schema/value rejection, references, inheritance, axes, collisions, deterministic CSS, browser bundling, CLI behavior, contracts, explicit component choices, state ordering, nested islands, hydration, late SSR recipe insertion and baseline compatibility. Phase 2 additionally verifies all three presets in a real browser, including light/dark, linked/inline delivery, form input, switches, dialogs/focus, menus and 390px layouts. See the Phase 2 report under docs/plans for results and screenshot locations. Phase 3 extends source hooks, contracts and recipes to all 104 components and verifies all catalog default states across three systems and two modes. See its implementation report for limits and evidence. Complete accessibility/visual certification remains separate work.
Local authoring
Run bun run design:studio from the repository root for the optional non-AI editor. See Design Studio for drafts, previews, imports, exports and application revisions. Applications continue using this engine without Studio runtime dependencies.
Packaging, shared designs and recovery
See RELEASE.md for installed Studio/MCP usage, deterministic shared design packages, explicit upgrades, storage migration and the local release workflow. Existing APIs and default component behavior remain available.