@emotion/react, styled-components, and styled-jsx are CSS-in-JS libraries that allow developers to write styles directly within JavaScript or React components, enabling dynamic styling based on props and state. sass is a mature CSS preprocessor that extends CSS with variables, nesting, and mixins, compiling to standard CSS before runtime. While sass relies on a separate build step and global scope, the CSS-in-JS solutions offer component-scoped styles, dynamic theming, and tighter integration with the React lifecycle, each solving the problem of style maintenance in large-scale applications through different architectural approaches.
Choosing how to style a React application is one of the most impactful architectural decisions a team makes. The options range from mature preprocessors like sass to modern CSS-in-JS libraries like @emotion/react, styled-components, and styled-jsx. Each approach solves the problems of scope, dynamic styling, and maintenance differently. Let's break down how they handle real-world engineering challenges.
@emotion/react gives you two main ways to write styles: the css prop for quick, inline styles or the styled factory for creating components. Both keep styles scoped to the component automatically.
// @emotion/react: Using the css prop
import { css } from '@emotion/react';
function Button({ primary }) {
return (
<button
css={css`
background: ${primary ? 'blue' : 'gray'};
color: white;
padding: 8px 16px;
`}
>
Click Me
</button>
);
}
styled-components forces a component-centric model. You define a styled component first, then use it. This keeps the style definition right next to the logic.
// styled-components: Creating a styled component
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
padding: 8px 16px;
`;
function App() {
return <Button primary>Click Me</Button>;
}
styled-jsx uses standard <style> tags inside your component but scopes them automatically to that component only. It feels very close to writing plain CSS.
// styled-jsx: Scoped style tag
function Button({ primary }) {
return (
<>
<button className={primary ? 'primary' : ''}>Click Me</button>
<style jsx>{`
button {
padding: 8px 16px;
color: white;
}
.primary {
background: blue;
}
:not(.primary) {
background: gray;
}
`}</style>
</>
);
}
sass relies on external .scss or .sass files. You import these files into your components, and styles are global unless you manually use naming conventions or CSS Modules.
/* sass: button.scss */
.button {
padding: 8px 16px;
color: white;
&.primary {
background: blue;
}
&:not(.primary) {
background: gray;
}
}
// Importing sass in React
import './button.scss';
function Button({ primary }) {
return <button className={`button ${primary ? 'primary' : ''}`}>Click Me</button>;
}
@emotion/react handles dynamic values directly in the template literal or object syntax. It re-computes styles when props change, but can be optimized by caching static parts.
// @emotion/react: Dynamic props
const width = 200;
<div css={css({ width: width, color: isActive ? 'red' : 'black' })} />
styled-components excels here by passing props directly into the styled template. It is very readable for complex conditional logic.
// styled-components: Prop interpolation
const Box = styled.div`
width: ${props => props.width}px;
color: ${props => props.isActive ? 'red' : 'black'};
`;
<Box width={200} isActive={true} />
styled-jsx requires you to use class names and toggle them based on props, or use template literals inside the style tag with careful variable injection. It is less direct for prop-based styling.
// styled-jsx: Class toggling for dynamics
function Box({ width, isActive }) {
return (
<>
<div className={`box ${isActive ? 'active' : ''}`} style={{ width }} />
<style jsx>{`
.box { color: black; }
.box.active { color: red; }
`}</style>
</>
);
}
sass handles logic at compile time using mixins and functions. It cannot react to runtime props directly. You must pass data via class names or inline styles for runtime changes.
/* sass: Mixin for reusable logic */
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.container {
@include flex-center;
// Runtime dynamics require inline styles or class toggles in JS
}
@emotion/react and styled-components both inject styles at runtime using JavaScript. This adds a small overhead when the component mounts, but enables powerful dynamic features. They both support server-side rendering (SSR) to send critical CSS with the initial HTML.
// Both libraries inject styles dynamically
// No extra build step needed for style processing
const StyledDiv = styled.div`color: red;`; // Injects on render
styled-jsx is unique because it extracts static styles at build time (especially in Next.js). This means zero runtime cost for static styles, making it very fast for content-heavy sites.
// styled-jsx: Static styles are extracted to CSS files during build
// Only dynamic parts (if any) incur runtime cost
<style jsx>{`div { color: red; }`}</style>
sass compiles everything to standard CSS during the build process. There is zero runtime overhead since the browser just loads a .css file. This is often the fastest option for pure performance if you don't need dynamic JS-based styling.
/* sass: Compiled to standard CSS before the app runs */
/* No JavaScript involved in applying these styles */
.div { color: red; }
@emotion/react and styled-components both provide robust ThemeProvider components. You can pass a theme object down the tree and access it anywhere in your styles.
// @emotion/react & styled-components: Theme usage
import { useTheme } from '@emotion/react'; // or styled-components
function ThemedBox() {
const theme = useTheme();
return <div css={css({ color: theme.colors.primary })} />;
}
styled-jsx supports theming but requires a bit more setup, often relying on CSS variables or custom providers to pass values into the style tags.
// styled-jsx: Using CSS variables for theming
function ThemedBox() {
return (
<>
<div className="themed">Content</div>
<style jsx>{`
.themed { color: var(--primary-color); }
`}</style>
</>
);
}
sass uses global variables and maps defined in SCSS files. Changing a theme usually means recompiling the CSS or swapping stylesheets. It lacks the runtime flexibility of JS-based themes.
/* sass: Global variables */
$primary-color: blue;
.themed {
color: $primary-color;
}
Despite their differences, these tools share common goals and capabilities.
All four approaches aim to keep styles organized. sass requires discipline (like BEM), while the others enforce scoping automatically.
// All allow modular component design
function Card() { return <div className="card">...</div>; }
sass, @emotion/react, styled-components, and styled-jsx all support nesting, making it easier to visualize hierarchy.
/* sass nesting */
.card { .title { color: red; } }
// styled-components nesting
const Card = styled.div`
.title { color: red; }
`;
All tools generate source maps to help you debug styles in the browser developer tools, linking generated CSS back to your original source files.
| Feature | @emotion/react | styled-components | styled-jsx | sass |
|---|---|---|---|---|
| Style Location | JS Props or Styled Components | Styled Components Only | <style> Tags | External .scss Files |
| Scoping | Automatic | Automatic | Automatic | Manual (Global by default) |
| Dynamic Props | Excellent | Excellent | Moderate (Class toggling) | None (Compile time only) |
| Runtime Overhead | Yes | Yes | Low (Static extraction) | None |
| Theming | Runtime Context | Runtime Context | CSS Variables / Context | Compile-time Variables |
| Best For | Flexible, mixed patterns | Strict component patterns | Next.js, performance | Legacy, global styles |
sass remains the king of stability and raw performance. If your team prefers keeping logic and styles separate, or if you are working on a project with heavy global stylesheets, sass is the safe, proven choice. It has no runtime cost and a massive ecosystem of mixins.
styled-components is the opinionated leader for React. If you want a strict "styles-as-components" workflow with excellent theming and dynamic prop support out of the box, this is the standard. It reduces boilerplate but locks you into its specific pattern.
@emotion/react offers the best of both worlds. It supports the styled component pattern but also lets you drop the css prop anywhere for quick tweaks. It is often faster and more flexible, making it ideal for large, complex applications where you need escape hatches.
styled-jsx is the specialist for Next.js. If you are using Next.js and care deeply about performance, its ability to extract static CSS at build time while keeping scopes local is unmatched. It feels the most like "regular CSS" but with superpowers.
Final Thought: There is no single "best" tool. If you need dynamic, prop-driven styles in a complex React app, reach for @emotion/react or styled-components. If you are building a content site on Next.js, styled-jsx is a strong contender. If you prefer traditional CSS workflows with zero runtime JS, sass is still incredibly powerful.
Choose @emotion/react if you need maximum flexibility and performance in a React application. It is ideal for teams that want to use both styled components and the css prop for inline styles without being locked into a specific component pattern. Its low-level API allows for easy integration with existing codebases and supports advanced features like server-side rendering optimization and style extraction.
Choose sass if your project relies heavily on global stylesheets, design tokens, or requires a strict separation between logic and presentation. It is the best fit for teams migrating from legacy CSS workflows or those who prefer writing standard CSS syntax with powerful preprocessing features like mixins and functions without the runtime overhead of JavaScript-based styling.
Choose styled-components if you prefer an opinionated, component-first approach where styles are tightly coupled with the component definition. It is excellent for teams that value a consistent developer experience with built-in theming, automatic vendor prefixing, and a robust ecosystem of plugins. This library shines in projects where dynamic styling based on props is a core requirement and you want to avoid managing class names manually.
Choose styled-jsx if you are building a Next.js application and want scoped CSS that feels like standard <style> tags. It is the optimal choice for projects prioritizing zero-runtime overhead for static styles while still needing component scoping. This library is less suitable for highly dynamic styles based on complex props but excels in performance-critical applications where minimizing JavaScript bundle size is a priority.
Simple styling in React.
yarn add @emotion/react
/** @jsx jsx */
import { jsx, css, Global, ClassNames } from '@emotion/react'
render(
<div css={{ color: 'hotpink' }}>
<div
css={css`
color: green;
`}
/>
<Global
styles={{
body: {
margin: 0,
padding: 0
}
}}
/>
<ClassNames>
{({ css, cx }) => (
<div
className={cx(
'some-class',
css`
color: yellow;
`
)}
/>
)}
</ClassNames>
</div>
)
More documentation is available at https://emotion.sh.