@soralabsoss/generator

Pure TypeScript library that turns a small BrandConfig (a few colors, a couple of fonts, some style knobs) into a full design-token system: OKLCH primitive ramps, semantic tokens with WCAG-AA-validated contrast pairings, and exporters for CSS, DTCG JSON, Tailwind, and a paste-ready shadcn / Tailwind v4 theme CSS.
It has no DOM dependencies — it runs in Node, in the browser, or inside the @sora-lattice/web Configurator that ships in this repo.
Why
Most shadcn/Tailwind theme tools (visual editors, preset pickers) hand you a palette and leave contrast and ramp consistency up to you. This package goes the other direction: give it one primary color and it derives a full OKLCH ramp plus every semantic pairing (background/foreground, border, interactive states, chart colors, ...), validates each pair against WCAG AA, and adjusts automatically when a step would fail. The output is deterministic and framework-agnostic — a plain function call, not a UI you have to drive — so it fits into a build step, a CLI, or a design-system pipeline just as well as into an app.
Install
npm install @soralabsoss/generator
Requires Node >=18.20.8. The package is ESM-only — package.json has no CJS require export condition, so require('@soralabsoss/generator') will not resolve; use import.
Workspace use (inside this monorepo)
The @sora-lattice/web package already depends on it via workspace:*. To consume it from another workspace package, add:
// package.json
"dependencies": {
"@soralabsoss/generator": "workspace:*"
}
The package is built to dist/ and exports its public surface from src/index.ts.
Quick start
One-shot: produce tokens and exported artifacts in a single call.
import { generateTheme } from '@soralabsoss/generator';
const theme = generateTheme(
{
primaryColor: '#2e7bab',
primaryFont: 'Inter',
headingFont: 'Cormorant Garamond',
},
{ formats: ['css', 'shadcn'], colorSpace: 'oklch' },
);
theme.tokens.light;
theme.tokens.dark;
theme.artifacts[0].content;
theme.artifacts[1].content;
Step-by-step: build the config, generate light/dark token sets, then export.
import {
createBrandConfig,
generateDesignTokens,
exportTokens,
validateWcagAaContrast,
} from '@soralabsoss/generator';
const config = createBrandConfig({ primaryColor: '#2e7bab' });
const light = generateDesignTokens(config, false).tokens;
const dark = generateDesignTokens(config, true).tokens;
const css = exportTokens({ light, dark }, 'css', 'oklch');
const failures = validateWcagAaContrast(light);
Public API surface
createBrandConfig(input) | Merges partial input with initialConfig defaults; safe for all-optional input. |
generateDesignTokens(config, isDark) | Returns { tokens, semanticMap }. semanticMap records which primitive each semantic token resolves to (used by the inspector UI). |
generateTheme(input, options) | Convenience wrapper: builds the config, both modes, and any requested export artifacts in one call. |
exportTokens(tokens, format, colorSpace, options?) | format: 'css' | 'dtcg' | 'tailwind' | 'shadcn'. colorSpace: 'hex' | 'rgb' | 'hsl' | 'oklch'. |
generateRamp, generateOklchRamp, generateNeutralRamp | Lower-level OKLCH ramp builders. |
getGeneratedColor(hex, mode) | Compute a complementary / triadic / analogous / etc. partner from a base color. |
validateWcagAaContrast(tokens, pairs?) | Returns failing ContrastValidationFailure[] for the default 16 semantic pairs, or for a custom list. |
pickContrastingFg(bg, ramp, isDark) | Walk a ramp until a step meets WCAG AA against a background. |
generateSkills(config, tokens) | Generates agent-facing skill markdown (tokens/theming, component creation, accessibility) describing the emitted token system. |
NAMED_HUES, STEPS, NEUTRAL_STEPS, GENERATION_MODES, SEMANTIC_HUES | Constant tables consumed by the generator and re-exported for UI use. |
Types: BrandConfig, BrandConfigInput, ColorRamp, NeutralColorRamp, TokenSet, ExportFormat, ColorSpace, PrimitiveMapping, ContrastPair, ContrastValidationFailure, GenerationMode.
generateSkills (in skills.ts) lives in this package rather than a separate one because it reads the exact BrandConfig/TokenSet shapes this package produces to generate accurate, config-specific documentation (real token names, configured density/roundness/headless-lib, etc.) — it's a consumer of this package's own output, not an unrelated concern bolted on. It's used by the Configurator's export flow (apps/web/src/components/Configurator/Export/export-page.tsx) to ship a .claude/skills-style bundle alongside generated tokens.
Source layout
src/
├── index.ts # Public exports + generateTheme()
├── types.ts # BrandConfig, defaults, createBrandConfig()
├── color-utils.ts # STEPS, ColorRamp types, generateRamp, helpers
├── color-generation.ts # OKLCH math: gamut clamp, Gaussian chroma, hue allocation
├── contrast-utils.ts # pickStep / pickContrastingFg
├── generate-tokens.ts # Semantic mapping → CSS custom properties (light + dark)
├── export-tokens.ts # Format-specific writers (CSS, DTCG, Tailwind, shadcn)
├── accessibility.ts # WCAG AA validation against the 16 default pairs
├── skills.ts # Agent/tool-facing skill descriptors for the generator API
└── culori.d.ts # Local types for the subset of culori we use
Scripts
Inside this monorepo (uses Turborepo/bun workspace filtering):
bun run --filter @soralabsoss/generator build
bun run --filter @soralabsoss/generator test
Working in this package directly (e.g. after cloning just this repo, or from an npm-installed copy for local hacking) — the filter above only resolves inside the monorepo workspace:
cd packages/generator
bun run build
bun run test
The test suite (test/*.test.ts) covers config normalization, deterministic token output, all four export formats, the AA contrast guarantees for both modes, and the pure color-math/contrast helpers in isolation.
Publishing
This package is published to npm as @soralabsoss/generator under MIT, so it can be consumed outside this monorepo (e.g. a CLI in another repo calling generateTheme() directly, with no network dependency).
To cut a release:
- Bump
version in package.json (semver — this is a public API surface once published).
- Push a tag matching
generator-v<version> (e.g. generator-v0.1.0) on main.
.github/workflows/publish-generator.yml builds, tests, verifies the tag matches package.json, then runs npm publish --access public --provenance.
Requires an NPM_TOKEN repository secret (npm automation token with publish rights on the @soralabsoss scope).
Implementation notes
- Color math is OKLCH-first via culori. Lightness targets in color-generation.ts are tuned so that primary/status backgrounds land near step 600 in light mode and step 400 in dark mode, which keeps neutral-0 foregrounds above 4.5:1 contrast without per-token overrides.
- Color-parsing helpers (
generateRamp, getContrastColor, adjustLightness, getGeneratedColor, etc.) fall back to a sensible default (the input hex, or a fixed black/white) instead of throwing when culori can't parse a color. This is deliberate: a single malformed color (e.g. mid-edit in a color picker) degrades one ramp/step rather than crashing the whole generateTheme() call. Validate user-supplied color strings before passing them in if you need hard failures.
- Semantic tokens are emitted as
var(--color-<hue>-<step>) references rather than literal hex, so consumers can edit primitives without rebuilding the whole token set.
- Exporters share a single category mapping (
primitive-color, background, border, foreground, interactive, chart, gradient, font, space, typography, dimension, shape, shadow, state, transition, other), so output ordering and grouping stay consistent across formats.