@leaflink/design-system
v1.1.27
Published
Multi-brand design-system color tokens and Tailwind theme
Keywords
Readme
Table of Contents
@leaflink/design-system
Multi-brand design tokens for the new LeafLink design system: colors (--ds-color-*) plus a base corner radius
(--ds-radius). Framework-agnostic CSS custom properties with adapters for Tailwind v4, shadcn/ui, Bootstrap/MDB, and
Angular Material. The shadcn adapter maps --ds-radius; the other adapters map colors only.
The
--ds-*prefix and the@leaflink/design-systemname are placeholders. Renaming either later is a find-and-replace across this package and each consumer's mapping — treat it as its own small migration.
Tiers
- Primitives — the raw OKLCH ramp (
--ds-color-blue-500, …). Source of truth for hue values. - Semantic roles — what you build against (
--ds-color-primary,--ds-color-surface,--ds-color-text, …), plus the non-color--ds-radius(base corner radius). - Data-viz — a categorical chart palette (
--ds-color-viz-1…-8), exempt from "use semantic".
Disabled is a state, not a role: there is no --ds-color-disabled-*. Style a disabled control by fading its enabled
fill in the component layer, the way Bootstrap, MDB and shadcn/ui already do, so a disabled destructive button stays
recognizably destructive instead of flattening to one shared neutral. Hover and active work the opposite way: they keep
dedicated roles (--ds-color-primary-hover, --ds-color-secondary-active, …), because a translucent derived fill has
no single contrast ratio the gate can hold. The reasoning is in
the architecture doc.
Brands and dark mode
A brand is a full set of values for the shared roles. Pick one with data-brand; pick light/dark with a .dark
class. Both go on the same element (usually <html>). Each product keeps its own look as a brand and shares only the
structure (role names, dark-mode logic, contrast gate) — there is no forced reskin.
- Default (unnamed). An unbranded
:rootresolves to it. This is the new design system's own look, for the new app. stash. A frozen snapshot of today's Stash values. Opt in with<html data-brand="stash">for a no-visual-change match to the current LeafLink apps. Light only: the Stash app has no dark mode.dama. DamaFE's current colors, kept as-is. Opt in with<html data-brand="dama">so DamaFE adopts the shared structure without changing its look. Light only: DamaFE has no dark mode.
Only the default brand ships dark values, so what .dark does on a light-only brand depends on the entry. On the
all-brands ./tokens entry it falls back to the default dark look through the cascade. On a single-brand entry
(./tokens/stash, ./tokens/dama) no dark values load at all, so it does nothing. Either way, don't toggle it.
The data-brand attribute and the .dark class both go on <html>. Each line below is one combination — they are
alternatives, not nested:
<html> default brand, light
<html class="dark"> default brand, dark
<html data-brand="stash"> stash brand, light
<html data-brand="dama"> dama brand, lightRe-pointing a brand
A consumer only references roles and pins one data-brand, so a brand's look changes in one place — its token file —
with no consumer change. For example, to re-point dama (edit brands/dama.css):
- Match the
stashlook: replace its brand-specific literals withstash's role values (thevar(--ds-color-<family>-<step>)references inbrands/stash.css).damathen renders the current LeafLink look and stays frozen with it. - Follow the
default: remove the overrides so[data-brand="dama"]resolves to:root, anddamatracks the design system's evolving look.
Usage
Pick one entry for the CSS variables:
@import '@leaflink/design-system/tokens/default'; /* one brand — also /stash, /dama */
@import '@leaflink/design-system/tokens'; /* all brands — only for switching brands at runtime */
@import '@leaflink/design-system/tailwind-theme'; /* Tailwind utilities: bg-primary, text-foreground, … */A single-brand entry (./tokens/<brand>) is the common case — smaller and self-contained. It defines its roles under
[data-brand="<brand>"] (except default, which is :root), so set data-brand on <html> (see the table above);
there is no :root fallback once the other brands aren't loaded. Use the all-brands ./tokens only when one page
switches brands at runtime.
The all-brands ./tokens entry already includes the chart palette. On a single-brand entry, add
@import '@leaflink/design-system/tokens/data-viz' for it. Either way it follows the active brand's ramp, so pair it
with a brand that carries the ramp (default or stash).
See each brand painted onto a sample UI in src/tokens/brands.
Framework adapters map the roles onto a library's own variable names — import one in place of the library's color block, after the library's CSS:
@import '@leaflink/design-system/shadcn-theme'; /* shadcn/ui: --background, --primary, --chart-1, … */
@import '@leaflink/design-system/bootstrap-theme'; /* Bootstrap & MDB: --bs-*/--mdb-primary, --bs-body-bg, … */
@import '@leaflink/design-system/angular-material-theme'; /* Angular Material: --mat-sys-primary, --mat-sys-surface, … */Semantic utilities flip under .dark on their own — they read variables that change value, so no Tailwind dark:
variant is required. To author explicit dark: overrides, add @custom-variant dark (&:where(.dark, .dark *)); at the
top level of your own Tailwind entry (not inside a layer()-wrapped import).
