FrontendJun 20263 min read

Css Custom Properties vs Css In Js

CSS custom properties are native, cascading, and runtime-themeable with zero JavaScript. CSS-in-JS gives you scoped, dynamic styles tied to component logic — at the cost of bundle weight and runtime overhead. For most teams in 2026, native variables win.

The short answer

Css Custom Properties over Css In Js for most cases. Custom properties are part of the platform — they cascade, theme at runtime, and ship zero JavaScript.

  • Pick Css Custom Properties if want runtime theming, dark mode, and design tokens that just work across any framework with no build step and no JS payload
  • Pick Css In Js if your styles genuinely depend on JS state or props in ways the cascade can't express, and you accept a zero-runtime compiler that still emits custom properties under the hood
  • Also consider: A zero-runtime solution like Vanilla Extract or Linaria — it gives you typed, colocated authoring while compiling down to static CSS plus custom properties, so you get the DX without the runtime.

— Nice Pick, opinionated tool recommendations

What they actually are

CSS custom properties (a.k.a. CSS variables) are a native language feature: --brand: #0af declared on any selector, read with var(--brand), inheriting and cascading like every other property. They live in the stylesheet and the browser resolves them at render time — change one on :root and every consumer updates instantly, no re-render, no JS. CSS-in-JS is an authoring pattern, not a standard: you write styles inside JavaScript (template literals or objects), and a library — styled-components, Emotion, Stitches, Vanilla Extract — turns them into real CSS, either at runtime by injecting <style> tags or at build time as static files. The key distinction is that custom properties are a primitive the platform owns, while CSS-in-JS is a tooling layer that, increasingly, just generates custom properties anyway. One is a feature; the other is a strategy for emitting features.

Runtime theming and dark mode

This is where custom properties stop being merely adequate and become decisive. Define your tokens on :root, override them under [data-theme='dark'], and the entire UI re-themes with a single attribute flip — no component re-render, no context provider, no prop drilling. The browser recomputes var() references natively, so it's faster and simpler than anything a library can offer. CSS-in-JS can do dynamic theming too, but runtime libraries pay for it: every themed value flows through a ThemeProvider, every prop-driven style triggers React reconciliation and fresh style injection. That's measurable jank on large trees. The cruel irony is that modern CSS-in-JS theming systems implement themes by generating custom properties under the hood — Stitches and Vanilla Extract both do exactly this. If the best version of your library reinvents the native feature, you can usually skip the library and use the native feature.

Performance and bundle cost

Custom properties cost nothing extra: they're in the CSS you already ship, parsed by the browser's optimized engine, zero added JavaScript. Runtime CSS-in-JS is the opposite story — you ship the library (Emotion is ~20KB, styled-components ~12KB gzipped), then pay at runtime to serialize styles, hash class names, and inject <style> tags during render. Under server rendering it gets nastier: you must extract critical CSS, and React 18 streaming famously broke naive runtime injection, which is part of why the whole category lost momentum. Custom properties have none of these failure modes — there's no hydration mismatch on a var(). Zero-runtime CSS-in-JS (Linaria, Vanilla Extract) closes the gap by compiling to static CSS, but at that point you've conceded the runtime model entirely. If raw performance is the question, native variables win without trying.

Scoping, DX, and where CSS-in-JS still earns its keep

CSS-in-JS's original pitch was scoping — no more global-namespace collisions — plus colocation and dead-code elimination. That was a genuine pain in 2017. It's mostly solved now by CSS Modules, scoped <style> in Vue/Svelte, and native @scope. Custom properties don't scope behavior, they scope values: set --gap on a component's root and children read it locally, which covers most real needs cleanly. Where CSS-in-JS still legitimately wins is styles that are truly a function of runtime JS — a width computed from a drag position, a color from a data value mid-render. You can do that by writing custom properties from JS (el.style.setProperty), which is the best-of-both move, but the ergonomics of typed, colocated, prop-driven styling are nicer in a library. That's a real niche. It is not most of your stylesheet, and it doesn't justify a runtime for the whole app.

Quick Comparison

FactorCss Custom PropertiesCss In Js
JavaScript payloadZero — native CSS feature12–20KB+ for runtime libs (zero for compiled)
Runtime theming / dark modeNative, instant, no re-renderWorks but often via re-render and injection
Style scopingScopes values, not selectorsAutomatic per-component scoping
SSR / streaming safetyNo hydration or extraction issuesRuntime libs need critical-CSS extraction; broke under React 18 streaming
Prop-driven dynamic stylesPossible via setProperty, less ergonomicFirst-class, typed, colocated

The Verdict

Use Css Custom Properties if: You want runtime theming, dark mode, and design tokens that just work across any framework with no build step and no JS payload.

Use Css In Js if: Your styles genuinely depend on JS state or props in ways the cascade can't express, and you accept a zero-runtime compiler that still emits custom properties under the hood.

Consider: A zero-runtime solution like Vanilla Extract or Linaria — it gives you typed, colocated authoring while compiling down to static CSS plus custom properties, so you get the DX without the runtime.

Css Custom Properties vs Css In Js: FAQ

Is Css Custom Properties or Css In Js better?

Css Custom Properties is the Nice Pick. Custom properties are part of the platform — they cascade, theme at runtime, and ship zero JavaScript. CSS-in-JS solved a real scoping problem in 2017 that scoped CSS, CSS Modules, and the cascade have since solved without a runtime tax. The ecosystem already voted: Meta killed runtime CSS-in-JS internally, styled-components went maintenance mode, and the survivors are all zero-runtime compilers that emit static CSS using custom properties anyway. Reach for the variable, not the library.

When should you use Css Custom Properties?

You want runtime theming, dark mode, and design tokens that just work across any framework with no build step and no JS payload.

When should you use Css In Js?

Your styles genuinely depend on JS state or props in ways the cascade can't express, and you accept a zero-runtime compiler that still emits custom properties under the hood.

What's the main difference between Css Custom Properties and Css In Js?

CSS custom properties are native, cascading, and runtime-themeable with zero JavaScript. CSS-in-JS gives you scoped, dynamic styles tied to component logic — at the cost of bundle weight and runtime overhead. For most teams in 2026, native variables win.

How do Css Custom Properties and Css In Js compare on javascript payload?

Css Custom Properties: Zero — native CSS feature. Css In Js: 12–20KB+ for runtime libs (zero for compiled). Css Custom Properties wins here.

Are there alternatives to consider beyond Css Custom Properties and Css In Js?

A zero-runtime solution like Vanilla Extract or Linaria — it gives you typed, colocated authoring while compiling down to static CSS plus custom properties, so you get the DX without the runtime.

🧊
The Bottom Line
Css Custom Properties wins

Custom properties are part of the platform — they cascade, theme at runtime, and ship zero JavaScript. CSS-in-JS solved a real scoping problem in 2017 that scoped CSS, CSS Modules, and the cascade have since solved without a runtime tax. The ecosystem already voted: Meta killed runtime CSS-in-JS internally, styled-components went maintenance mode, and the survivors are all zero-runtime compilers that emit static CSS using custom properties anyway. Reach for the variable, not the library.

Related Comparisons

Disagree? nice@nicepick.dev