styled-jsx

Scoped, component-friendly CSS for JSX that compiles with Babel and injects styles at runtime without global leakage.

Library
npm
v5.1.7
7,776stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
59/100Fair
Development Activity20
Maintenance48
Community68
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
69/100Good
Architecture78
Code Quality70
Innovation72
Learning Curve55

styled-jsx is Vercel’s CSS-in-JS solution for writing scoped, component-friendly CSS directly inside JSX, using a <style jsx> tag that a Babel plugin transforms into unique class names and runtime style injection. It ships as the default styling system inside Next.js, but works in any Babel-based React setup that adds styled-jsx/babel to its plugin list.

Under the hood, styled-jsx rewrites JSX at compile time to attach a unique jsx-<hash> class to each element inside a styled component, then extracts the CSS block into a _JSXStyle runtime component that injects the rules via the CSSOM (or a plain server-rendered <style> tag during SSR). This keeps runtime overhead tiny (about 3kb gzipped) while preserving full CSS syntax — media queries, keyframes, vendor prefixing, and dynamic values interpolated from props.

What You Get

  • <style jsx> compiler - a Babel plugin that rewrites JSX and generates uniquely scoped class names at build time.
  • Runtime style injection - the styled-jsx/style component injects generated CSS via the CSSOM on the client or a plain tag during SSR.
  • Global and external style APIs - <style jsx global>, css.resolve, and css.global helpers for styling outside a single component.
  • CSS preprocessing plugin hooks - support for Sass/Less/PostCSS-style plugins that run before the scoping transform.

Common Use Cases

  • Component-scoped styling in Next.js apps - zero-config CSS-in-JS for teams that don’t want a separate CSS Modules or Tailwind pipeline.
  • Dynamic, prop-driven styling - interpolating component props directly into CSS without hand-rolling class-name toggling.
  • Component libraries that ship their own styles - using css.resolve/external-stylesheet patterns so consumers get styles without importing a separate CSS file.
  • Server-rendered apps needing critical CSS - collecting only the styles used per request via StyleRegistry for SSR flush.

Under The Hood

Architecture styled-jsx splits into two pieces: a Babel plugin (src/babel.js, src/_utils.js) that performs a JSX AST transform at compile time, and a small runtime (src/index.js, src/stylesheet-registry.js, src/lib/stylesheet.js, src/lib/hash.js) that a bundler consumes to inject styles at runtime. The babel plugin’s visitor pattern (JSXOpeningElement/JSXElement enter and exit in src/babel.js) walks the JSX tree, calling findStyles/getJSXStyleInfo/computeClassNames from _utils.js to locate <style jsx> blocks, hashing their contents via src/lib/hash.js, and rewriting matching JSX elements to carry the generated class. The runtime side is a React context (StyleSheetContext in stylesheet-registry.js) wrapping a StyleSheetRegistry that dedupes styles by instance count and delegates actual CSS injection to the StyleSheet class in lib/stylesheet.js, explicitly modeled after Glamor’s sheet implementation, choosing between CSSOM insertRule/deleteRule and a plain style-tag path depending on environment. This compile-time/runtime split keeps the shipped runtime surface small while AST-level complexity lives entirely in the Babel plugin.

Tech Stack The project’s own build is authored in plain JavaScript (not TypeScript, despite hand-written .d.ts files for consumers) using Babel 7 for the AST transform, bundled with bunchee into separate CJS entry points for the babel plugin, webpack loader, and runtime. Its package.json declares a single runtime dependency, client-only, and treats react, @babel/core, and babel-plugin-macros as (optional) peer dependencies so both the Babel and webpack-loader integration paths stay externally pluggable. Tests run under ava with @babel/register, and TypeScript consumers are served via hand-authored declaration files rather than a source-level TS build. CI runs on a current Node LTS with pnpm, executing build, lint, test, and a separate type-check pass, plus a semantic-release step on protected branches.

Code Quality Test coverage is substantial — dozens of fixture files paired with snapshot tests exercise the Babel transform against a wide range of real-world JSX patterns (dynamic styles, global selectors, external stylesheets, fragments, macros) via ava’s snapshot testing, plus a separate type-check pass over the declaration files. Error handling in the plugin favors codeframe-style compiler diagnostics for malformed <style jsx> usage, and invariant-style thrown errors in the runtime registry/stylesheet classes rather than silent failures. The code is vanilla ES2015+ JavaScript, linted with a fairly permissive config, and formatted with prettier plus pre-commit hooks; naming is consistent but some core files carry limited inline documentation.

API Design The public API is deliberately minimal — a single <style jsx> (or <style jsx global>) tag with no additional imports needed once the Babel plugin is registered, unlike CSS-in-JS libraries that require a tagged-template import at every call site. This compile-time scoping approach keeps consumer code visually identical to plain CSS with zero component wrapping, and the opt-in resolve/global/registry escape hatches only surface when a project needs SSR critical-CSS extraction or shared stylesheets — otherwise the getting-started path is a single plugin-array entry.

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