tss-react

Type-safe CSS-in-JS styling for React, powered by Emotion, with first-class MUI support

Library
npm
v4.9.21
722stars
MIT License

Repository Health

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

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
71/100Good
Architecture74
Code Quality68
Innovation72
Learning Curve70

tss-react is a dynamic CSS-in-TS solution built on top of Emotion, offering a type-safe equivalent of the classic JSS makeStyles/withStyles API without requiring any custom styling syntax — you write plain CSS objects and get full TypeScript inference for rule names, theme values, and props. It was created to give teams migrating away from Material-UI v4’s makeStyles (and JSS-based libraries in general) a drop-in-feeling replacement that keeps JSX readable by isolating style definitions from component structure, while still allowing inline styles when that’s simpler.

Beyond basic styling, tss-react adds features JSS never had cleanly: deterministic control over class-name specificity to resolve CSS cascade conflicts, a typed equivalent of JSS’s nested-selector $ruleName syntax, seamless SSR support for both the Next.js Pages Router and App Router, and the ability to plug in a custom Emotion cache. It ships as a lightweight layer over @emotion/react (~5kB minzipped alongside MUI) and is explicitly designed so that library authors can depend on it without forcing it into consumers’ peerDependencies.

What You Get

  • tss.create() / makeStyles() hooks that return typed classes, css, and cx for defining and consuming rule-based stylesheets
  • A type-safe port of the JSS nested-selector $ruleName syntax via withNestedSelectors, for referencing sibling classes inside a rule
  • Built-in specificity control to force certain classes to take precedence over others without resorting to !important
  • First-class MUI integration, including an improved withStyles for migrating off Material-UI v4, and theme-aware style hooks
  • Dedicated SSR entry points for both the Next.js Pages Router (tss-react/next/pagesDir) and App Router (tss-react/next/appDir)
  • Support for a custom, user-supplied Emotion cache via TssCacheProvider for advanced caching or CSP nonce scenarios

Common Use Cases

  • Migrating a codebase off Material-UI v4’s makeStyles/withStyles onto a maintained, typed styling solution
  • Styling React components with plain CSS objects while keeping full type-checking on rule names and theme access
  • Server-rendering styled React apps in Next.js (Pages or App Router) without style flash-of-unstyled-content issues
  • Building component libraries that need styling without forcing a specific CSS-in-JS runtime onto consuming apps
  • Resolving CSS specificity/cascade conflicts between multiple applied classes in a deterministic, code-level way

Under The Hood

Architecture tss-react is organized as a set of factory functions rather than a monolithic hook: createTss, createMakeStyles, and createWithStyles (see src/tss.ts, src/makeStyles.tsx, src/withStyles.tsx) each close over a useTheme accessor and an optional Emotion cache, and return the actual React hooks consumers call. At the base of that chain sits createUseCssAndCx (src/cssAndCx.ts), which wraps Emotion’s low-level serializeStyles/insertStyles/getRegisteredStyles primitives to produce the css/cx functions everything else composes on top of, including a WeakMap-keyed specificity-fix mechanism (increaseSpecificityToTakePrecedenceOverMediaQueries) that resolves a documented cascade-ordering bug (issue #27) by re-deriving class-to-CSSObject mappings per Emotion cache. Framework-specific entry points (src/nextJs.tsx, src/dsfr.ts, src/mui-compat.ts) are thin adapters over this same core, letting the package expose separate tss-react/next/pagesDir and tss-react/next/appDir subpath exports for SSR without duplicating styling logic.

Tech Stack The package is pure TypeScript (100% of the codebase per GitHub’s language breakdown) built with tsc twice — once for CommonJS (tsconfig.json) and once for ESM (tsconfig-esm.json) — plus a small ts-node build step (shim_jsx_element.ts) to patch JSX typing gaps. Its only real runtime dependencies are Emotion internals (@emotion/serialize, @emotion/utils, @emotion/cache, pinned as *), with react, @emotion/react, and @mui/material declared as peer dependencies (the last two optional) so it never forces a specific Emotion or MUI version on consumers. Demo/integration apps under src/test/apps/{spa,ssr,next-appdir} pull in Create React App and Next.js 13 to exercise SSR and client rendering paths during development.

Code Quality There is no runtime test suite (no *.test.* files); correctness is instead validated through TypeScript-only “type tests” under src/test/types/ (e.g. tss.tsx, makeStyles.tsx, withStyles_classes.tsx) that assert the generic type inference compiles as expected, plus manual verification via the three demo apps. Source files favor small, single-responsibility modules (mergeClasses.ts, types.ts, a tools/ folder with focused helpers like getDependencyArrayRef.ts and isSSR.ts) and consistent JSDoc @see links back to the hosted docs site, but the heavy use of advanced conditional/mapped TypeScript generics (visible throughout src/tss.ts) raises the bar for contributors modifying the type layer. ESLint + Prettier + Husky pre-commit hooks (lint-staged) enforce formatting consistency.

API Design The public surface is deliberately close to JSS’s makeStyles/withStyles, which minimizes new syntax for teams migrating off Material-UI v4, and getting started requires only wrapping a useTheme accessor in createMakeAndWithStyles (or using the pre-bound default tss export for theme-less usage). Subpath exports (tss-react/mui, tss-react/next/appDir, tss-react/cssAndCx, tss-react/compat) cleanly separate optional integrations so consumers only pull in what they use, and most public functions carry doc-site links directly in JSDoc comments for in-editor discoverability. The tradeoff is that the exported TypeScript types themselves are dense generic constructs, which can produce intimidating type errors for newcomers despite the runtime API being simple.

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