@dloizides/design-tokens
v1.0.1
Published
Framework-agnostic design-token SOURCE for the dloizides.com portfolio. One typed token set per app (aml / erevna / katalogos), seeded byte-for-byte from each app's CURRENT values, emitted in three forms from one source: CSS custom properties (:root) for
Maintainers
Readme
@dloizides/design-tokens
Framework-agnostic design-token source for the dloizides.com portfolio — the cross-stack unifier (task #78). One typed token set per app, seeded byte-for-byte from each app's current values, emitted in three forms from one source.
#1 rule — behaviour-preserving extraction, not a redesign. Every preset mirrors the app's existing look. Adopting this package changes nothing visually. Runtime tests assert the RN output deep-equals the app's current
DEFAULT_THEME_CONFIGand the AML CSS reproducesui.css's:rootverbatim.
Values only — no components. (Shared React components live in @dloizides/ui-layout,
ui-forms, ui-feedback, ui-icons, ui-tables.)
The three output forms
One DesignTokens object per app feeds three pure emitters:
| Form | Emitter | Consumer stack | Output |
|------|---------|----------------|--------|
| (a) CSS custom properties | toCssVars(tokens, names, opts?) | Stack-A vanilla-JS (AML console) | a :root { --…: …; } string (+ optional dark-mode block) |
| (b) RN-web theme object | toRnTheme(tokens) | RN-web (erevna, katalogos) | a TenantThemeConfig-compatible object |
| (c) Tailwind preset | toTailwindPreset(tokens) | DOM-React (SyncfusionThemeStudio) | { theme: { extend: { colors, borderRadius, boxShadow, fontFamily } } } |
import {
AML_TOKENS, EREVNA_TOKENS, KATALOGOS_TOKENS, PRESETS,
toCssVars, toRnTheme, toTailwindPreset,
AML_CSS_VAR_NAMES, DEFAULT_CSS_VAR_NAMES,
} from '@dloizides/design-tokens';
// (a) AML console — reproduces wwwroot/shared/ui.css :root exactly
const css = toCssVars(AML_TOKENS, AML_CSS_VAR_NAMES);
// (b) RN-web — drop in as the tenant-theme fallback (deep-equals the current default)
const rnDefault = toRnTheme(EREVNA_TOKENS);
// (c) DOM-React ThemeStudio — spread into tailwind.config.presets
const tw = toTailwindPreset(KATALOGOS_TOKENS);The per-app presets
Each preset is seeded from that app's current source of truth:
| Preset | Seeded from | Notes |
|--------|-------------|-------|
| EREVNA_TOKENS | erevna-web/src/theme/presets/default.ts + theme/utils/palette.ts | primary #005f73, secondary #94d2bd, accent #008d5c; full light+dark mode |
| KATALOGOS_TOKENS | katalogos-web/src/theme/presets/default.ts | currently identical to Erevna; kept separate so they can diverge |
| AML_TOKENS | PROOViD/AMLService/.../wwwroot/shared/ui.css :root | brand #4f46e5; dark app-shell + light workspace + verdict palette + radii/shadows/fonts |
RN presets carry no typography/radii/shadows/extensions — the RN theme has no
centralized tokens for those today, so they are omitted (nothing to emit → zero change). The AML
preset carries the richer superset (dark app-shell, ink scale, verdict palette, radii, shadows,
fonts) in its extensions group.
The token shape (aligned to TenantThemeConfig)
The core fields intentionally mirror @dloizides/tenant-theme-web's TenantThemeConfig
(primary, secondary, accent, semantic, light, dark, typography, branding) so a
per-tenant theme override layers cleanly on top of a preset — the tenant blob and the preset speak
the same field names. Fields the core cannot express (a dark app-shell, an ink scale, sanctions
verdict triads) live in an optional extensions group and are documented as Stack-A supersets,
not part of the TenantThemeConfig contract.
The cross-stack naming contract (#78b)
Different stacks use different CSS-variable names for the same token. toCssVars takes a
name map (CssVarNames) that says which CSS variable each token field emits as; a field mapped
to undefined is skipped. Two maps ship:
DEFAULT_CSS_VAR_NAMES— canonical kebab names (--primary,--background,--radius, …), with a dark-mode block. Adopt these for a fresh consumer.AML_CSS_VAR_NAMES— the AML console's existingui.cssnames, so nothing there changes.
AML name-mapping table (core → ui.css variable)
| Token field | AML CSS var | Note |
|-------------|-------------|------|
| primary | --brand | |
| secondary, accent | (skipped) | AML is single-brand; ui.css never reads them |
| semantic.success/warning/error | (via --ok/--warn/--bad) | emitted from the verdict extension |
| semantic.info | (skipped) | no AML variable |
| light.background | --bg | |
| light.surface | --surface | |
| light.surfaceElevated | (via --surface-2) | emitted from workspace.surfaceAlt |
| light.text | (via --ink) | emitted from ink.base |
| light.textSecondary | (via --ink-soft) | emitted from ink.soft |
| light.border | --line | |
| light.divider | (skipped) | AML reuses --line |
| dark.* | (skipped) | AML has no light/dark toggle; its dark shell is the shell extension in :root |
| radii.{xs,sm,md,lg,pill} | --r-xs, --r-sm, --r, --r-lg, --r-pill | |
| shadows.{sm,md,pop} | --sh-1, --sh-2, --sh-pop | |
| typography.fontFamily / monoFamily | --font / --mono | |
| extensions.shell.* | --shell, --shell-2, --shell-line, --on-shell, --on-shell-dim | |
| extensions.brand.* | --brand-press, --brand-soft, --brand-ring | |
| extensions.ink.* | --ink, --ink-soft, --ink-faint, --ink-2 | |
| extensions.workspace.* | --surface-2, --line-strong | |
| extensions.verdict.* | --ok*, --warn*, --bad* (base/soft/ink) | |
Open naming decisions (need a human call before wider adoption)
These were resolved conservatively to guarantee zero change, but a human should confirm the intended long-term semantics:
- AML
secondary/accent— AML has a single brand. The preset mirrors--brandinto both so the core shape is complete, butui.cssreads neither. If AML ever gains a real secondary brand, set it here. - AML
dark= the app-shell. AML has a dark shell over a light workspace, not a light/dark mode toggle. The preset seedsdarkfrom the shell so the shape is complete, and the AML name map omitsdark*so no dark-mode block is emitted (the shell is emitted from theshellextension in:root). If a real AML dark mode is introduced, this needs revisiting. semantic.info— AML has noinfocolour; it mirrors--brand. Confirm before relying oninfoin AML.- RN
radii/shadows. The RN apps have no centralized radius/shadow tokens today, so the RN presets omit them. If RN adopts a radius/shadow scale later, decide whether it should share AML's values or define its own.
Adoption (separate tasks — this package ships values only)
This package does not modify any consuming app. Adoption is tracked separately:
- AML: replace the hand-written
:rootinui.csswithtoCssVars(AML_TOKENS, AML_CSS_VAR_NAMES)output. - erevna/katalogos: import
toRnTheme(EREVNA_TOKENS)as thedefaultThemeConfigfallback. - ThemeStudio: spread
toTailwindPreset(...)intotailwind.config'spresets.
Scripts
npm run build (tsup → CJS+ESM+d.ts), npm test (jest, 100% coverage), npm run lint,
npm run typecheck. Publish via ./publish.ps1 patch|minor|major.
