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.

Library
npm
v8.2.0
12,348stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
85/100Excellent
Development Activity80
Maintenance96
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
83/100Excellent
Architecture78
Code Quality80
Innovation83
Learning Curve90

@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 css tagged-template tag for writing CSS with Sass-like nesting, extracted to static CSS at build time
  • The cx helper for composing and deduplicating class names, including Linaria’s atomic (atm_-prefixed) class output
  • A typed CSSProperties type for object-style CSS authoring alongside the tagged-template syntax
  • The CssProcessor/TaggedTemplateProcessor extraction primitive that @linaria/react and @linaria/atomic build 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search