@serenis/design-tokens
v0.22.0
Published
Design tokens for the Serenis design system. Generates typed JS constants, CSS custom properties, cssvar accessor helpers, and CSS utility classes — all from a single set of [DTCG](https://tr.designtokens.org/format/) JSON files.
Downloads
229
Keywords
Readme
@serenis/design-tokens
Design tokens for the Serenis design system. Generates typed JS constants, CSS custom properties, cssvar accessor helpers, and CSS utility classes — all from a single set of DTCG JSON files.
See ADR-0011 for architectural decisions.
Install
npm install @serenis/design-tokensImport the CSS bundle in your app entry point:
import '@serenis/design-tokens/css'Sub-paths for advanced use cases: @serenis/design-tokens/css/base, @serenis/design-tokens/css/serenis-light, @serenis/design-tokens/css/serenis-dark, @serenis/design-tokens/utilities.
Quick start
# Build all outputs (codegen + CSS + JS bundle)
yarn workspace @serenis/design-tokens build
# Run tests (includes golden snapshot tests)
yarn workspace @serenis/design-tokens test
# Update golden snapshots after intentional changes
yarn workspace @serenis/design-tokens test -- -uDirectory structure
libraries/design-tokens/
├── tokens/ # DTCG JSON source files
│ ├── root.json # Base tokens (all types)
│ ├── serenis-light.json # Light theme overrides
│ ├── serenis-dark.json # Dark theme overrides
│ ├── aliases.json # Semantic alias tokens
│ ├── tablet.json # Responsive overrides (≥601px)
│ └── desktop.json # Responsive overrides (≥1025px)
├── src/
│ ├── stories/ # Storybook stories and documentation (colocated)
│ │ ├── Breakpoints/
│ │ ├── Color/ # Color stories + Overview.mdx doc page
│ │ ├── Dimensions/
│ │ ├── Effects/
│ │ ├── Typography/
│ │ ├── UtilityClasses/
│ │ ├── components/ # Shared story helper components
│ │ └── docs/ # Domain-specific doc helpers for MDX pages
│ │ ├── components/ # Category-specific doc components (ColorPairing, etc.)
│ │ └── utils/ # Token description extractors (reads $description from JSON)
│ └── utils/ # Hand-written runtime helpers (compiled → dist/utils/)
├── scripts/
│ ├── pipeline/ # Build pipeline entry + Style Dictionary config
│ ├── codegen/ # TypeScript module generators
│ ├── cssUtilities/ # CSS utility class generator
│ └── shared/ # JSON reader, token key sorting
├── dist/ # Generated JS/DTS modules + CSS (gitignored)
│ ├── types/ # Token name arrays + union types
│ ├── constants/ # UPPER_SNAKE_CASE constants (var refs + raw values)
│ ├── cssvar/ # Typed accessor functions
│ ├── utils/ # Compiled from src/utils/
│ └── *.css # CSS custom properties + utility classes
└── package.jsonDocumentation pages
MDX files under src/stories/ are picked up by Storybook as doc pages (configured in apps/design-system/.storybook/main.ts). These pages use design-token-based typography via docs-reset.css, which overrides Storybook's built-in styles.
Reusable building blocks for doc pages (SectionIntro, DosDonts, Table/Row, CodeExample) come from the storybook-docs library. Category-specific visual components (e.g. ColorPairing) live in src/stories/docs/components/. Token $description metadata is read by src/stories/docs/utils/tokenDescriptions.ts, so doc pages stay in sync with the JSON source automatically.
Story sort order puts "Overview" pages first, then "Playground", then everything else.
Writing an Overview page
Every token category should have an Overview.mdx file that follows a consistent structure. Use Color/Overview.mdx as the reference implementation.
Expected sections:
- Title + intro — Category name as
# heading, one-line summary, then<SectionIntro>pulling$descriptionfrom the token JSON source. - Theme and responsive behavior — How these tokens behave across themes and viewports. Every Overview page includes this section for consistency.
- Conceptual model — How the tokens in this category are organized (layers, scales, naming logic). Use
<Table>for structured breakdowns. - Token-by-token guide with code examples — Walk through the main tokens/groups with usage guidance, rendered values,
<CodeExample>blocks, and<DosDonts>. Code examples and guidelines are interleaved within each token subsection rather than gathered at the end. - Accessibility / pairing notes (when relevant) — Contrast, motion, sizing, or other a11y considerations for this category. Not every category needs a dedicated section.
Imports:
import { Meta } from '@storybook/addon-docs/blocks'
import { CodeExample, DosDonts, Row, SectionIntro, Table } from 'storybook-docs'
import { rootDescriptions } from '../docs'
// For themed tokens (Color, Effects):
import { rootDescriptions, lightDescriptions, darkDescriptions } from '../docs'Generic building blocks (SectionIntro, DosDonts, Table/Row, CodeExample) come from the storybook-docs library. Category-specific components (e.g. ColorPairing) live in src/stories/docs/components/. Token descriptions are pre-computed maps in src/stories/docs/utils/tokenDescriptions.ts — use rootDescriptions.spacing, lightDescriptions.color, etc. to get $description strings. This keeps docs in sync with the JSON source automatically.
How to consume
1. JS constants (styled-components)
import { COLOR_PRIMARY, SPACING_MD, BORDER_RADIUS_LG } from '@serenis/design-tokens'
const Card = styled.div`
background: ${COLOR_PRIMARY};
padding: ${SPACING_MD};
border-radius: ${BORDER_RADIUS_LG};
`Each constant resolves to var(--srns-spacing-md). A _VALUE companion holds the raw CSS value:
SPACING_MD // "var(--srns-spacing-md)"
SPACING_MD_VALUE // "16px"2. CSS custom properties
@import '@serenis/design-tokens/css'; /* base + both themes + utility classes */
/* or granular: */
@import '@serenis/design-tokens/css/base'; /* root + responsive overrides */
@import '@serenis/design-tokens/css/serenis-light'; /* base + light theme (standalone) */
@import '@serenis/design-tokens/css/serenis-dark'; /* base + dark theme (standalone) */All variables are prefixed --srns- and scoped to :root (base) or .srns-serenis-light / .srns-serenis-dark (themes).
3. Cssvar accessor helpers
import { cssvarSpacing } from '@serenis/design-tokens'
cssvarSpacing('md')
// → { name: 'srns-spacing-md', value: 'var(--srns-spacing-md)' }Typed: cssvarSpacing only accepts valid SpacingName values.
4. CSS utility classes
/* app.css */
@import '@serenis/design-tokens/utilities';This gives you utility classes mapped to design tokens: bg-primary-50, p-md, px-lg, mb-sm, rounded-lg, elevation-sm, font-600, etc. Typography composites are available as typography-paragraph, typography-public-h1, etc. Directional spacing uses short Tailwind-style names (px/py/pl/pr/pt/pb for padding, mx/my/ml/mr/mt/mb for margin), mapped to CSS logical properties under the hood. No external CSS framework required; the output is plain CSS compatible with Safari 16+.
Responsive variants
Some utility classes are available with a mobile-first breakpoint prefix, following the pattern {bp}\:{utility}. Three breakpoints are supported:
| Prefix | Media query |
| ------ | ------------------- |
| md | min-width: 601px |
| lg | min-width: 1025px |
| xl | min-width: 1441px |
For example, md\:flex applies display: flex when the viewport is at least 601px wide.
Utilities with responsive variants:
- Spacing padding (
p-*,px-*,py-*,pl-*,pr-*,pt-*,pb-*) - Spacing margin (
m-*,mx-*,my-*,ml-*,mr-*,mt-*,mb-*) - Spacing gap (
gap-*,gap-x-*,gap-y-*) - Display (
flex,grid,block,inline-flex,inline-block,hidden) - Flex direction (
flex-row,flex-col) - Alignment (
items-center,items-start,items-end,items-stretch) - Justify (
justify-center,justify-between,justify-start,justify-end) - Flex wrap (
flex-wrap,flex-nowrap) - Flex grow/shrink (
grow,grow-0,shrink,shrink-0) - Flex basis (
basis-1/12–basis-11/12,basis-full,basis-auto,basis-{container})
Utilities without responsive variants: spacing sizing (w-*, h-*, size-* from spacing 0 only), container width (w-{container}, min-w-{container}, max-w-{container}), border radius (rounded-*), colors (bg-*, text-{color}, border-{color}), border width (border-*), elevation (elevation-*), z-index (z-*), font weight, font family, opacity (opacity-*), typography composites (typography-*), position, overflow, text-align, border-style, and transition.
cx helper
The build also generates a cx() helper for composing utility classes. It is typed via UtilityClassName, so you get autocomplete and compile-time checks. It also accepts object arguments for conditional classes — keys whose value is truthy are included in the output:
import { cx } from '@serenis/design-tokens'
;<div className={cx('p-md', 'bg-primary-50', 'rounded-lg')} />
;<div className={cx('pt-md', { [cx('pt-0', 'bg-primary-50')]: isLarge })} />Linting: Two ESLint rules enforce cx() usage across the monorepo. enforce-cx-classname requires cx() for all className composition (no raw strings or template literals). cx-classnames-order enforces a deterministic CSS-property-based ordering of class arguments and removes duplicates — this rule is auto-fixable.
How to add a token
Add to JSON: Edit the appropriate file in
tokens/(usuallyroot.json). Follow the DTCG format:{ "my-branch": { "$type": "dimension", "$description": "Description of this token group", "my-token": { "$value": { "value": 8, "unit": "px" } } } }Build: Run
yarn workspace @serenis/design-tokens build. The pipeline auto-discovers branches by$type.Update snapshots: Run
yarn workspace @serenis/design-tokens test -- -uto update golden snapshots.The new token is immediately available as:
- JS constant:
MY_BRANCH_MY_TOKEN/MY_BRANCH_MY_TOKEN_VALUE - CSS variable:
--srns-my-branch-my-token - CSS utility class (e.g.
my-branch-my-token)
- JS constant:
How to add a theme
Create a new JSON file in
tokens/with color/elevation overrides.Register it in
scripts/pipeline/themeTokenRegistry.ts:{ id: 'my-theme', tokenJsonFileName: 'my-theme.json', colorScheme: 'light' }Rebuild. The pipeline produces
dist/my-theme.cssand theme-specific JS constants automatically.
Token types
| DTCG $type | Token branches | Codegen |
| ------------ | --------------------------------------------------------------------------------------------------- | ------------------------ |
| dimension | spacing, container, border-radius, border-width, font-size, line-height, letter-spacing, breakpoint | Registry (simple) |
| duration | time | Registry (simple) |
| fontFamily | font-family | Registry (simple) |
| fontWeight | font-weight | Registry (simple) |
| number | opacity, z-index | Registry (simple) |
| string | media-query | Registry (simple) |
| shadow | elevation | Dedicated (theme layers) |
| color | color | Dedicated (theme layers) |
| typography | typography, typography.public | Dedicated (composite) |
"Registry (simple)" types share a unified codegen path via SIMPLE_TOKEN_RESOLVERS. Adding a new simple type means adding one entry to the registry in scripts/codegen/tokenTypeResolvers.ts.
Build pipeline
tokens/*.json
│
├──▶ Custom codegen ──▶ dist/{types,constants,cssvar,utils}/*.{js,d.ts}
│ │
│ └──▶ esbuild CJS bundle ──▶ dist/index.cjs
│
├──▶ Style Dictionary ──▶ dist/root.css, dist/{theme}.css, dist/tablet.css, dist/desktop.css
│ │
│ └──▶ Minify + assemble ──▶ dist/base.css, dist/serenis-light.css, dist/serenis-dark.css
│
├──▶ Style Dictionary (utility classes format) ──▶ dist/utilities.css
│ │
│ └──▶ Extract class names ──▶ dist/types/utility-class.{js,d.ts} (UtilityClassName)
│ + dist/utils/cx.{js,d.ts} (cx helper)
│
└──▶ Concat base + themes + utilities + minify ──▶ dist/index.cssScripts
| Command | Description |
| ---------------- | ------------------------------------------------------ |
| build | Full build: codegen + Style Dictionary CSS + JS bundle |
| test | Run all tests (includes golden snapshot tests) |
| test:watch | Watch mode |
| typecheck | TypeScript validation |
| lint | ESLint |
| lint:fix | ESLint with auto-fix |
| prettify | Prettier formatting |
| prettify:check | Check Prettier formatting without writing |
| clean | Remove .turbo, build, dist, node_modules |
License
PolyForm Noncommercial 1.0.0 — see LICENSE for the full text.
