Design Tokens
A design token is a named design decision β a colour, a spacing step, a font
size β stored once and referenced everywhere. Sulphuris is built around exactly
this idea: the maps in core/_config.scss are your
token set. You declare them once and the whole utility layer is generated from
them.
There are two flavours of token in Sulphuris, and the distinction matters:
- Compile-time tokens β sizes, spacing, breakpoints, type scale, borders.
These are baked into the generated class values at build time.
.p-16ships aspadding: 16px, notpadding: var(--space-16). - Runtime tokens β the themeable layer. Colours (
--color-*, see [[color]]), font stacks (--font-heading,--font-paragraph,--font-mono), shadows (--shadow-*), transitions (--transition-duration,--transition-easing) and the component radius (--radius) are also emitted as CSS custom properties on:root, so they can be re-themed live (dark mode, per-section overrides) without recompiling.
The config maps are the tokens
Everything a project needs to brand Sulphuris lives in a handful of maps:
$sizes: 0, 4, 8, 16, 24, 32, 48, 64; // spacing / sizing scale
$breakpoints: ('lg': 1024px, 'md': 768px, 'sm': 480px);
$colors: (foreground: #1a1a1d, background: #fff, primary: #824f2d);
$palettes: (blue: #0f4eb3, gray: #8c8c8e); // each seeds a 100β900 ladder
$typography: ( 'h1, .h1': (desktop: (96px, -1.5px, 1, bold)), β¦ );
$border-radiuses: 0, 4, 6, 8, 16, 24, 32;
Override them before @use "sulphuris" and every generated class follows
your tokens β see Getting Started and
Configuration for the full list.
@forward "sulphuris/core/config" with (
$sizes: (0, 4, 8, 16, 24, 32, 48, 64),
$colors: (
foreground: #111111,
background: #ffffff,
primary: #0057ff,
)
);
@use "sulphuris";
Runtime colour tokens
Colours are the biggest runtime family. Each key in $colors and each
generated palette grade becomes a --color-* variable on :root:
:root {
--color-foreground: #1a1a1d;
--color-background: #ffffff;
--color-primary: #f6c026;
--color-blue-500: #0f4eb3;
/* β¦100β900 for every paletteβ¦ */
}
Because the utilities reference them through var(), you can consume the same
tokens in your own hand-written CSS β no SCSS import required:
.callout {
color: var(--color-foreground);
background: var(--color-blue-100);
border: 2px solid var(--color-primary);
}
And you can retheme them at runtime by re-declaring the variables under any
scope. This is exactly how dark mode works β [data-color-scheme="dark"]
re-emits the --color-* set (see [[color]]):
[data-color-scheme="dark"] { --color-background: #1a1a1d; }
.brand-section { --color-primary: #ff3366; }
The other runtime tokens
Beyond colour, the themeable single-value tokens ship as custom properties on
:root too, and the utilities that carry them read through var():
:root {
--font-heading: Roboto, sans-serif; /* headings, .font-heading */
--font-paragraph: Nunito, sans-serif; /* body, .font-paragraph */
--font-mono: monospace; /* .font-mono, prose code */
--transition-duration: 250ms; /* .transition*, transition() defaults */
--transition-easing: cubic-bezier(0.86, 0, 0.07, 1);
--shadow-sm: 0 1px 2px rgb(0 0 0 / 5%); /* β¦one per $shadows key, .shadow-* */
--radius: 0.25rem; /* .rounded β see below */
}
--radius is the component corner radius. Its Sass side, $border-radius, is
an alias into $border-radiuses β it points at a step the .rounded-* ladder
already carries (the build warns otherwise), so the token can't drift off the
scale. Re-declare any of these under a scope to retheme it, exactly like a
colour:
.marketing-hero { --font-heading: 'Archivo Black', sans-serif; }
.compact-ui { --radius: 0; --transition-duration: 100ms; }
Note
Spacing, sizing, the type scale and breakpoints remain compile-time β the
value is the class name (.p-16 is 16px by definition), so a live variable
would have nothing honest to vary. If you need spacing as live CSS
variables, emit your own --space-* set alongside Sulphuris (see below).
Consuming tokens in your SCSS
Inside SCSS, reach for the config maps and helpers rather than repeating raw values, so your components stay tied to the same tokens as the utilities:
@use "sulphuris/core/config" as config;
@use "sulphuris/core/utils/helpers" as helpers;
@use "sass:list";
.card {
color: helpers.color(primary); // β var(--color-primary)
padding: list.nth(config.$sizes, 5); // β 16px, from the shared scale
}
helpers.color($name) returns the var(--color-*) reference (with the raw
value as fallback); helpers.get-color($name, $mode) returns the raw compile-time
value. See Functions & Mixins.
Overriding & supplementing tokens with poops
Sulphuris is transpiled with poops, which has a
design-tokens step
of its own: you author tokens once as JSON and @use them straight from SCSS via
the token: prefix (both W3C DTCG and Style Dictionary formats are auto-detected).
Because it runs at transpile time, you can feed those tokens into β or extend β the
Sulphuris config, keeping a single JSON source of truth for the build.
Author the tokens as JSON, e.g. src/tokens/colors.json:
{
"color": {
"$type": "color",
"primary": { "$value": "#0057ff" },
"secondary": { "$value": "#ff6600" },
"link": { "$value": "{color.primary}" }
}
}
Point poops' tokenPaths at that directory in poops.json:
{
"styles": [
{ "in": "src/scss/index.scss", "out": "dist/css/styles.css",
"options": { "tokenPaths": ["src/tokens"] } }
]
}
poops exposes each file as token:<filename>, flattened to $color-* variables.
Override β feed those tokens into the config forward so they win for the
build, rebranding the whole utility layer per target/theme without hand-editing
_config.scss:
@use "token:colors" as c;
@forward "sulphuris/core/config" with (
$colors: (primary: c.$color-primary, foreground: #111, background: #fff)
);
@use "sulphuris";
Prefer a single map to spread? Set "tokenOutput": "map" in the poops options
and read it with map.get(c.$color, primary).
Supplement β emit token families Sulphuris bakes in at compile time
(spacing, sizing, the type scale) as live custom properties alongside the
built-in --color-* / --font-* / --shadow-* set, straight from the same
JSON. This is the poops-driven way to get the --space-* variables the
note above says Sulphuris does not emit itself:
@use "token:spacing" as s;
:root {
--space-16: #{s.$space-16};
--space-24: #{s.$space-24};
}
The integration point on the Sulphuris side is unchanged β the config maps in
core/_config.scss remain the source of truth; poops just
lets you feed and extend them from a shared JSON source instead of the stylesheet.
Bringing in external token sources
If your tokens are authored elsewhere β Style Dictionary, Figma Tokens, Tokens Studio, a design-system JSON β you don't hand them to Sulphuris directly. Export them to SCSS variables/maps and feed those into the config forward. The config map is the single integration point:
// tokens.generated.scss (produced by Style Dictionary et al.)
$brand-primary: #0057ff;
$space-scale: (0, 4, 8, 16, 24, 32, 48, 64);
// your entry stylesheet
@use "tokens.generated" as t;
@forward "sulphuris/core/config" with (
$colors: (primary: t.$brand-primary, foreground: #111, background: #fff),
$sizes: t.$space-scale
);
@use "sulphuris";
Keep the token export as the source of truth; Sulphuris becomes the layer that
turns those tokens into utility classes and --color-* custom properties.