Skip to content

Design Tokens

Michael Dörer edited this page Oct 5, 2026 · 3 revisions

Design Tokens

All design decisions of a project live in srl.config.json in the project root. You never write colors, font sizes or breakpoints by hand – you define them once as tokens and use them everywhere.

What happens with the tokens

Design token pipeline

srl.config.json
      │  srl beaver (runs automatically in `npm run dev` and on build)
      ▼
srl/config.scss        → tokens as Sass maps
srl/colors.scss        → one function per color       colors-primary-1000()
srl/typography.scss    → one mixin per text style     typography-paragraph()
      │
      ▼  every output (app, ldd, pdf, word, xbrl) includes init-root + core-styles
:root { --srl-… }      → CSS custom properties (per breakpoint)
.srl-…                 → utility classes (see Utility Classes)

In your SCSS you only need:

@use 'srl';

.my-box {
  @include srl.typography-paragraph();
  color: srl.colors-primary-1000();
  @include srl.spacer-margin-top(400);
}

After changing srl.config.json, the dev server regenerates everything automatically. Run npx srl beaver by hand only outside the dev server.


colors

"colors": {
  "colors": [
    { "name": "primary-1000", "color": "#F05000" },
    { "name": "on-primary-1000", "color": "#ffffff" },
    { "name": "shade", "color": "#6C757D" }
  ]
}
Generates Example
CSS variable --srl-color-primary-1000: #F05000
SCSS function srl.colors-primary-1000() → var(--srl-color-primary-1000)
Utility classes .srl-color-primary-1000, .srl-bg-primary-1000

Conventions:

  • on-<name> is the text color to use on top of <name>. .srl-bg-primary-1000 then also sets color to on-primary-1000.
  • shade: if a color named shade exists, the colors shade-50 … shade-950 are generated from it automatically, unless you define them yourself.

typography

Each entry is a text style:

{
  "name": "paragraph",
  "font-family": "Inter",
  "font-size": 16,
  "line-height": 1.5,
  "font-weight": 400,
  "letter-spacing": 0,
  "text-transform": "none",
  "color": "black-1000",
  "margin-top": 0,
  "margin-bottom": 0,
  "media": {
    "print": { "font-size": "9pt" },
    "up":    { "desktop": { "font-size": 18 } },
    "down":  { "phone-ls": { "font-size": 15 } }
  }
}
  • Unitless numbers are pixels and are converted to the build's unit: rem for app/editor/pdf, pt for Word.
  • line-height without a unit becomes em.
  • color refers to a color name from colors.
  • media can contain the following keys:
    • print, or any breakpoint name on its own: applies only in that range
    • up.<breakpoint>: applies from that breakpoint upwards
    • down.<breakpoint>: applies up to that breakpoint
Generates Example
CSS variables --srl-typo-paragraph-font-size, -line-height, -font-family, -font-weight, -font-style, -letter-spacing, -text-transform, -font-color, -margin-top, -margin-bottom
SCSS mixin @include srl.typography-paragraph(); or with margins srl.typography-paragraph(true)
Utility class .srl-typo-paragraph

Because the values are CSS variables, you can override a text style locally:

.my-teaser { --srl-typo-paragraph-font-size: 1.25rem; }

spacer

spacer.spacer – the spacing scale

"spacer": {
  "100": { "size": 8,  "media": { "print": { "size": "4pt" } } },
  "400": { "size": 32, "media": { "print": { "size": "16pt" }, "up": { "desktop": { "size": 40 } } } }
}
Generates Example
CSS variable --srl-spacer-400
SCSS srl.spacer-get(400), @include srl.spacer-margin-top(400) …
Utility classes .srl-mt-400, .srl-pa-100, .srl-gap-200 …

If no print value is set, the screen value is also used for print.

spacer.margins.group – spacing between components

Margin groups define the vertical spacing between neighbouring components:

"margins": {
  "group": {
    "text": {
      "all": 200,
      "title-h2": 800,
      "image": 800
    }
  }
}

This reads as: after an element with class .srl-margin-group-text, the next element gets margin-top: spacer 200. If the next element is a .srl-title-h2 or .srl-image, it gets spacer 800 instead.

Margin groups

  • all stands for any following element (*).
  • Plain names become .srl-<name>. Values that start with ., #, [, : or * are used as selectors as written.

To apply a group, give the component the class and include the mixin in its SCSS:

<p class="srl-paragraph srl-margin-group-text">…</p>
@include srl.spacer-component-margin(text);

The mixin also handles nested containers, the aside container, XBRL wrapper elements and the PDF split elements (-first / -last).

For one-off rules you can pass a map instead of a group name:

$margins: (
  ('.srl-lead', '*'): 1600,
  ('.srl-lead', '.srl-button-container--search'): 400,
);
@include srl.spacer-component-margin($margins);

grid

"grid": {
  "breakpoints": { "print": 0, "phone-pt": 0, "phone-ls": 576, "tablet-pt": 768, "tablet-ls": 992, "desktop": 1200, "desktop-large": 1400 },
  "containers":  { "phone-pt": { "padding": 16 }, "desktop": { "padding": 32, "max-width": 1354 } },
  "columns":     { "print": 8, "phone-pt": 4, "tablet-pt": 8, "desktop": 12 },
  "gutter":      { "print": { "column-gap": "16pt" }, "phone-pt": { "column-gap": 16 }, "desktop": { "column-gap": 32 } }
}
  • breakpoints are minimum widths in px. The breakpoint with 0 (besides print) is the base and is not wrapped in a media query.
  • For gutter, use gap for both directions, or row-gap / column-gap separately.
Generates Varies per breakpoint
--srl-container-max-width, --srl-container-padding ✔
--srl-gutter-columns, --srl-gutter-column-gap, --srl-gutter-row-gap ✔
--srl-breakpoint-<name>
Mixins srl.grid-media-up(desktop) … see SCSS Mixins and Functions

meta

Free-form settings for components, such as table paddings, list marker widths, quote characters or PDF margins. No CSS is generated from them. Components read them in SCSS:

@use 'sass:map';
@use 'srl';

$quote-open: map.get(srl.$meta, quote, quote-open);

// or with the helper functions
@if srl.is((pdf, margin)) {
  margin-top: srl.get((pdf, margin, top));
}

Put project-specific component settings here instead of hard-coding them in component SCSS.

fonts (optional)

"fonts": {
  "font-base-path": "src/assets/fonts",
  "fonts": ["Inter"]
}

Each entry is a folder below font-base-path that contains a styles.json. @font-face rules are generated from it.

Build variables

Each build adds system.build (app, editor, pdf, word, xbrl) and system.environment. Use them to write output-specific SCSS:

@if srl.$system-build == 'pdf' {
  break-inside: avoid;
}

Because Word does not support CSS variables, srl.system-root-style() and all token functions return the resolved value in Word builds (print values first) instead of var(--…).

Clone this wiki locally