@oshon-ai/tokens
v0.9.3
Published
Oshon design tokens — CSS custom-property contract, Tailwind v4 @theme bridge, and a three-seed palette runtime for white-labeling in a single DOM write.
Downloads
580
Maintainers
Readme
@oshon-ai/tokens
Oshon's token layer — the single source of truth for every color, dimension, and motion value the design system consumes at runtime.
- Three-seed palette runtime —
generateCorePalette({ primary, secondary, tertiary })produces four 10-shade ramps (primary / secondary / tertiary / tinted-gray, 44 hexes total) from any three hex seeds. applyTheme()— writes the resolved palette to any DOM node as--oshon-color-*custom properties. White-labeling in a single DOM write.- Token contract (
--oshon-*) — the full CSS custom-property surface every Oshon component consumes. No component in@oshon-ai/componentsor@oshon-ai/datahard-codes a color, dimension, or duration — they all resolve through this layer. - Tailwind v4
@themebridge — Tailwind utilities (bg-primary-500,rounded-md,shadow-lg) compile againstvar(--oshon-*), soapplyThemeretint a whole Tailwind-styled app atomically.
License: MIT.
Install
pnpm add @oshon-ai/tokensUsage
1. Install the token contract
Import the static CSS once at your app entry:
// Next.js: app/layout.tsx
import '@oshon-ai/tokens/css';Declares every --oshon-* custom property on :root plus dark-mode ([data-oshon-theme='dark']), AA focus-ring ([data-oshon-a11y='AA']), and prefers-reduced-motion blocks.
2. (Optional) Wire Tailwind v4
/* app/globals.css */
@import '@oshon-ai/tokens/css';
@import '@oshon-ai/tokens/tailwind';
@import 'tailwindcss';Now class="bg-primary-500 shadow-md rounded-lg" resolves against Oshon's token values.
3. White-label at runtime
import { applyTheme } from '@oshon-ai/tokens';
// call once at boot, or whenever the consumer picks a new brand
const teardown = applyTheme({
primarySeed: '#0ea5e9', // teal
secondarySeed: '#f59e0b', // amber
tertiarySeed: '#a855f7', // violet
});
// ... later
teardown(); // restores previous valuesEvery --oshon-color-primary-*, --oshon-color-secondary-*, --oshon-color-tertiary-*, and --oshon-color-gray-* shade updates atomically. Because Oshon components consume var(--oshon-color-primary-500) (not a build-time color), nothing re-renders — it's a pure style recalculation.
API
import {
generateCorePalette, // (seeds?) => CorePalette
paletteToCssVars, // (palette) => Array<[cssVar, hex]>
applyTheme, // (overrides, target?) => teardown
DEFAULT_PRIMARY_SEED, // '#000000'
DEFAULT_SECONDARY_SEED,// '#757575'
DEFAULT_TERTIARY_SEED, // '#e0e0e0'
type CorePalette,
type Ramp,
type Shade,
type PaletteSeeds,
type ThemeOverrides,
type Theme,
} from '@oshon-ai/tokens';Subpath entry points if you need a narrower import surface:
import { generateCorePalette } from '@oshon-ai/tokens/palette';
import { applyTheme } from '@oshon-ai/tokens/theme';Contract
- Hex seeds are validated at the boundary. Invalid input (
'red','#abcde',null) throws with a descriptive error naming which seed is wrong. No silent NaN propagation. - Short-form
#RGBexpands to#RRGGBB.#f0a===#ff00aa. - Grayscale seeds stay grayscale.
primarySeed: '#000000'produces a pure gray ramp (R=G=B at every shade) and a non-tinted gray family. - Colored seeds floor at usable saturation. Primary at sat 55+, secondary/tertiary at sat 40+, so pale seeds still produce legible mid-tones.
applyThemeis SSR-safe. Whendocumentis undefined it's a no-op returning a no-op teardown.- Server component safe for import.
palette.tsis pure and re-exportable from RSC.theme.ts(DOM-touching) carries'use client'and the tsup bundle re-injects it.
Versioning
Tokens is part of the MIT trio (@oshon-ai/tokens + @oshon-ai/primitives + @oshon-ai/components) whose versions are linked via Changesets. A token change bumps all three so consumers never see a skew between the token contract and the components that consume it.
Migration
Legacy @saas-ui/core ships the same runtime under the --sui-* prefix. During the SaaS-UI → Oshon transition both surfaces coexist; parity.test.ts in this package asserts every legacy --sui-* token has an --oshon-* counterpart so drift can't land silently. See OSHON.md Phase 2 for the migration plan and docs/NAMING_MIGRATION.md §4 for the rename dictionary.
