Linaria
Zero-runtime CSS-in-JS — write styles in tagged template literals that compile away to static CSS files, shipping no styling runtime to the browser.
Repository Health
Technical Analysis
@linaria/core is the framework-agnostic foundation of Linaria, a zero-runtime CSS-in-JS library maintained by Callstack. It exposes the css tagged-template function and the cx class-name composition helper, both of which are extracted and compiled away at build time by the WyW (wyw-in-js) Babel/bundler integration rather than executed in the browser — so styles ship as plain CSS files with none of the client-side style-injection cost that runtime CSS-in-JS libraries like styled-components or Emotion carry.
Because @linaria/core only defines the tag surface and a processor contract, other Linaria packages build on it directly: @linaria/react adds the styled component API, and @linaria/atomic adds atomic-class output that cx already knows how to deduplicate. Linaria 8 requires Node.js 22.12+ and is built on WyW 2’s hybrid evaluation strategy, which resolves statically provable values before falling back to full module evaluation for anything dynamic.
What You Get
- The
csstagged-template tag for writing CSS with Sass-like nesting, extracted to static CSS at build time - The
cxhelper for composing and deduplicating class names, including Linaria’s atomic (atm_-prefixed) class output - A typed
CSSPropertiestype for object-style CSS authoring alongside the tagged-template syntax - The
CssProcessor/TaggedTemplateProcessorextraction primitive that@linaria/reactand@linaria/atomicbuild on
Common Use Cases
- Component-scoped styling in shared UI libraries and design systems
- Migrating off runtime CSS-in-JS libraries (styled-components, Emotion) to cut client-side styling weight
- Static CSS extraction for server-rendered apps that need pre-built stylesheets instead of per-request style computation
- CSS-variable-driven dynamic theming without a runtime style-recalculation cost
Under The Hood
Architecture
Linaria’s @linaria/core package is intentionally thin at runtime: packages/core/src/index.ts exports only css, cx, and the CSSProperties/LinariaClassName types, with css.ts implementing the tag as a function that throws at runtime unless overridden (returning a mocked incrementing class name only in test mode) — the real work happens at build time via packages/core/src/processors/css.ts, whose CssProcessor class extends TaggedTemplateProcessor from the external @wyw-in-js/processor-utils package to turn each tagged template into extracted CSS rules (extractRules) and a generated class name (asSelector/value), with doEvaltimeReplacement/doRuntimeReplacement controlling how the original tag call is replaced in compiled output. This processor-per-tag pattern is registered declaratively via the wyw-in-js.tags field in package.json, which is how sibling packages like @linaria/react’s styled tag share the same evaluation/extraction pipeline without @linaria/core needing to know about them directly — the heavier lifting (module evaluation, dependency graph resolution, CSS ordering) is delegated entirely to the external wyw-in-js project Linaria 8 was rearchitected on top of, so a breaking change there ripples through every processor in lockstep.
Tech Stack
The monorepo (pnpm workspaces across packages/* plus a website package) is written in TypeScript and built with tsup for dual CJS/ESM output plus tsc --emitDeclarationOnly for type declarations, orchestrated across packages by Turborepo. @linaria/core itself carries almost no runtime dependency surface beyond a pinned @wyw-in-js/processor-utils, while the build-time transform machinery lives in the separate wyw-in-js.dev project, which is why Linaria 8 requires Node.js 22.12+. Testing runs on Jest, linting on ESLint with @typescript-eslint, and releases go through Changesets with commit messages enforced by commitlint and a pre-commit Husky hook; CI runs a full Ubuntu/Windows, Node 22.x/24.x matrix on GitHub Actions with pnpm-cached installs.
Code Quality
@linaria/core has a small but real Jest test suite (cx.test.ts, detect-core-js.test.ts) covering the cx helper’s falsy-value filtering and atomic-class deduplication with direct assertions rather than snapshots. Source files are small, single-purpose, and fully typed, including a cx overload interface that distinguishes an all-LinariaClassName call from the general string case, and the project runs a dedicated tsc --noEmit typecheck step plus dtslint to validate the published .d.ts files against real TypeScript usage scenarios — a level of type-contract testing many libraries skip. Runtime misuse paths (calling css/cx where the build-time plugin hasn’t run) throw descriptive errors rather than failing silently, and lint/typecheck/tests all run in CI and via a pre-commit hook before anything lands.
API Design
The public surface is deliberately minimal — one tagged-template function and one composition helper, usable with zero configuration beyond installing the bundler plugin — so getting started is import-and-write-CSS with none of the provider/theme-wrapper boilerplate that some runtime CSS-in-JS libraries require. Published, dtslint-validated .d.ts types give editors accurate autocomplete for cx’s overloads without any runtime type-checking cost, and naming (css, cx) mirrors conventions from other CSS-in-JS libraries, shortening the learning curve for anyone switching over. An extensive docs/ tree covering basics, API, dynamic styles, theming, and a dedicated migration guide addresses the one genuinely non-obvious setup step — wiring up the build-time transform — in detail; the main remaining friction is that a misconfigured plugin surfaces as a runtime error rather than a build-time diagnostic pointing at the missing setup.