How to css variables vs sass vs less for typography tokens
- Step 1Generate native CSS custom properties — Run the tool — its output is a native `:root` block (`--font-size-base: 1.000rem;` etc.), MIME `text/css`. This is the modern default and what you'll use as-is for any new project.
- Step 2Decide if you actually need a preprocessor — If you need runtime theming (dark mode, density, multi-brand at runtime), stay native. If your build does heavy compile-time math, loops, or mixins, you may keep Sass — but typography tokens specifically benefit from native's runtime mutation.
- Step 3If staying native: paste and reference — `font-size: var(--font-size-lg)`. Theming via `[data-theme]` or media queries. No further tooling. This is the recommended path for new code.
- Step 4If maintaining Sass: transcribe the values — The tool doesn't emit Sass. Convert `--font-size-base: 1rem;` to `$font-size-base: 1rem;`. Beware: Sass variables compile away, so you lose runtime theming. A hybrid (emit Sass `$` vars that set CSS `--` props) keeps both.
- Step 5If maintaining Less: transcribe to @ sigil — Convert `--font-size-base` to `@font-size-base`. Less interpolates at compile time. Same runtime-theming caveat applies. Less is legacy in 2026; prefer native for anything new.
- Step 6Set up theming on the native layer — Add `[data-theme="dense"] :root { --font-size-base: 0.875rem; }` next to the generated block. Components referencing `var(--font-size-base)` re-render instantly — the payoff native gives that preprocessors can't.
Native CSS vs Sass vs Less for typography tokens
The three approaches on the dimensions that matter for type tokens. This tool emits the first column.
| Dimension | Native CSS custom properties | Sass variables | Less variables |
|---|---|---|---|
| Syntax | --font-size-base: 1rem | $font-size-base: 1rem | @font-size-base: 1rem |
| Lives at | Runtime (in the browser) | Compile time (gone after build) | Compile time (gone after build) |
| Runtime mutation | Yes (JS, media query, scope) | No | No |
| Theming | Re-bind a scope; instant | Recompile per theme | Recompile per theme |
| DOM scoping | Inheritable, per-subtree | Global / module scope only | Global / scope only |
| Build step | None required | Sass compiler | Less compiler |
| Emitted by this tool | Yes | No (transcribe) | No (transcribe) |
Which to pick by scenario
Decision guide. For typography tokens specifically, native wins most scenarios because theming is the common case.
| Scenario | Recommended | Why |
|---|---|---|
| New project | Native CSS | No reason to add a preprocessor for tokens in 2026 |
| Dark mode / multi-brand at runtime | Native CSS | Runtime re-binding; preprocessors can't |
| Existing Sass codebase, no theming | Sass (or hybrid) | Migration cost may exceed benefit; hybrid keeps options open |
| Heavy compile-time math/mixins | Sass + native hybrid | Sass for math, native -- props for theming |
| Legacy maintenance | Less | Keep what's there; don't migrate for its own sake |
Transcribing the generated tokens
Same values, three sigils. The tool gives you the native form; converting is a find-replace of the prefix.
| Native (generated) | Sass | Less |
|---|---|---|
--font-size-base: 1rem; | $font-size-base: 1rem; | @font-size-base: 1rem; |
--font-weight-bold: 700; | $font-weight-bold: 700; | @font-weight-bold: 700; |
var(--font-size-lg) | $font-size-lg | @font-size-lg |
Cookbook
The same token, expressed three ways, and the theming behaviour that separates them. Block one is generated; the others are transcriptions.
Generated native CSS (the tool's output)
ExampleNative custom properties — runtime, inheritable, themeable. This is what the generator produces.
:root {
--font-size-base: 1.000rem;
--font-size-lg: 1.250rem;
--font-weight-bold: 700;
--line-height-normal: 1.5;
}
body { font-size: var(--font-size-base); line-height: var(--line-height-normal); }
h2 { font-size: var(--font-size-lg); font-weight: var(--font-weight-bold); }Same tokens transcribed to Sass
ExampleManual transcription — the tool does not emit Sass. Compiles to static values; no runtime theming.
$font-size-base: 1rem;
$font-size-lg: 1.25rem;
$font-weight-bold: 700;
$line-height-normal: 1.5;
body { font-size: $font-size-base; line-height: $line-height-normal; }
h2 { font-size: $font-size-lg; font-weight: $font-weight-bold; }
/* After build, $font-size-base is gone — only `1rem` remains. */Same tokens transcribed to Less
ExampleLess @ sigil. Also compile-time. Shown for completeness; Less is legacy in 2026.
@font-size-base: 1rem;
@font-size-lg: 1.25rem;
@font-weight-bold: 700;
body { font-size: @font-size-base; }
h2 { font-size: @font-size-lg; font-weight: @font-weight-bold; }The theming difference
ExampleWhy native wins for tokens. Dark mode is one extra block with native; a whole second compiled file with Sass.
/* Native — instant, no rebuild */
[data-theme="compact"] :root {
--font-size-base: 0.875rem;
}
/* every var(--font-size-base) shrinks at runtime */
/* Sass equivalent — recompile a second stylesheet */
// compact.scss: $font-size-base: 0.875rem; @import 'base';
// ship base.css AND compact.css, swap <link> to switchHybrid: Sass that emits native props
ExampleBest of both for existing Sass codebases. Sass does compile-time math; native props carry runtime theming.
// keep Sass for math
$base: 16px;
$ratio: 1.25;
:root {
// emit NATIVE custom props so theming still works at runtime
--font-size-base: #{$base / 16px}rem; // 1rem
--font-size-lg: #{($base * $ratio) / 16px}rem; // 1.25rem
}
h2 { font-size: var(--font-size-lg); }Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Expecting a Sass/Less export from the tool
Not supportedThe output is native CSS custom properties only (text/css). There's no Sass or Less export. Transcribing is a find-replace of the prefix (-- → $ or @) and changing var(--x) references to $x/@x — but you lose runtime theming when you do.
Sass variable used inside a media query expecting runtime change
Won't updateSass variables are resolved at compile time, so @media (prefers-color-scheme: dark) { $size: 1.1rem; } does nothing useful at runtime. Native custom properties are the only one of the three that re-bind per media query. This is the classic trap when migrating typography to dark mode.
Less variable scoping surprise
Lazy evalLess variables use lazy evaluation — the last definition in scope wins, even if it appears after usage. Transcribing native tokens to Less can produce surprising values if a later @font-size-base redefinition exists. Native custom properties follow normal cascade/inheritance, which is more predictable for tokens.
var() fallback vs preprocessor default
Different mechanismNative supports var(--font-size-base, 1rem) for a runtime fallback if the property is undefined. Sass/Less have no runtime equivalent — an undefined $var is a compile error. When transcribing, drop the var() fallback (it has no Sass/Less analogue) or handle missing values at build time.
Performance worry about var() resolution
NegligibleResolving var() costs microseconds on modern engines. The 2017 concern about CSS-variable performance doesn't reproduce in 2026 benchmarks for typography tokens. Don't choose Sass over native for a performance reason that no longer exists.
Gradual migration leaving both systems live
SupportedYou can ship the generated native tokens alongside existing Sass and migrate components incrementally. Both coexist fine — a component reading $font-size-lg and another reading var(--font-size-lg) produce the same px as long as the values match. Keep them in sync during the transition.
calc() with custom properties vs Sass math
Both work, differentlyNative uses calc(var(--font-size-base) * 1.5) at runtime; Sass computes $font-size-base * 1.5 at compile time. For tokens that must respond to runtime theme changes, native calc() is required — Sass math freezes the value at build.
em/rem unit handling across the three
Identical outputAll three emit the same rem/em strings — the generator's 1.250rem is 1.250rem whether you keep it native or transcribe it. The unit semantics are a CSS concern, not a preprocessor one, so transcription doesn't change rendered sizes.
Frequently asked questions
What does this tool actually output — CSS, Sass, or Less?
Native CSS custom properties (a :root block, text/css). It does not emit Sass or Less. If you need those, transcribe the values by changing the -- prefix to $ (Sass) or @ (Less) and updating references — but you forfeit runtime theming.
Is there a runtime performance difference?
Negligible. Modern browsers resolve var() in microseconds. The performance debate from 2017 doesn't hold up in 2026 benchmarks for typography tokens. Choose on theming and build-step grounds, not speed.
Why are native custom properties better for theming?
Runtime mutation. [data-theme="dark"] :root { --font-size-base: 1.1rem; } re-binds the token for a subtree at runtime, instantly. Sass/Less variables compile away, so theming means shipping and swapping multiple compiled stylesheets.
Can I migrate from Sass to native gradually?
Yes. Ship the generated native tokens alongside your Sass and migrate components one at a time. Both styles coexist; keep the numeric values in sync. Over time, components move from $font-size-lg to var(--font-size-lg).
What about the hybrid approach?
Use Sass for compile-time math/mixins, but have it emit native custom properties (--font-size-base: #{...}). You keep Sass's authoring power and gain native's runtime theming. The cookbook shows the pattern.
Does the generated output use rem or px?
rem, relative to your base size. --font-size-base is 1.000rem. This is the same regardless of which sigil you transcribe to — units are a CSS concern, not preprocessor-specific.
Is Less still worth using in 2026?
Mostly for legacy maintenance. New projects should use native custom properties. Less variables are compile-time like Sass but Less has a smaller ecosystem now; transcribe to Less only if you're maintaining an existing Less codebase.
Can Sass variables respond to a user's dark-mode preference?
Not at runtime. A Sass $var inside @media (prefers-color-scheme: dark) is resolved at compile time and won't change live. Only native custom properties re-bind per media query — which is why dark-mode typography pushes teams toward native.
Do I lose the var() fallback when I transcribe?
Yes. var(--font-size-base, 1rem) has no Sass/Less equivalent — an undefined preprocessor variable is a compile error, not a runtime fallback. Drop the fallback or ensure the variable is always defined at build time.
Will the same scale produce the same numbers in all three?
Yes — transcription only changes the prefix, not the value. --font-size-lg: 1.25rem and $font-size-lg: 1.25rem render identically. Generate once, transcribe if needed, and the type sizes stay the same.
Should a brand-new project ever start with Sass for tokens?
Generally no — start native. Add Sass only if you have a concrete compile-time need (complex math, mixins) that custom properties can't cover, and even then use the hybrid so theming stays runtime.
Is the generation done server-side?
No. It's a browser-only generative tool — no upload, no account. The native CSS is built locally; any Sass/Less transcription you do afterward is on your machine too.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.