Skip to content

Exploring the state of CSS-in-JS solutions

Posted on:August 30, 2023

CSS-in-JS is a pattern where CSS is composed using JavaScript, which allows you to manage your component styles in a more modular way and leverage the full power of js within your styles, such as dynamic styling based on props or state.

If you want to go the whole CSS-in-JS route it can be extremely overwhelming trying to decide which solution to go with. Let’s go over some solutions with the highest retention rate according to the recent 2023 State of CSS survey.

2023 State of CSS survey

CSS Modules

CSS Modules is a tried and true technique that allows you to write CSS in separate files but scope the styles to individual components. What’s also nice about it is that it’s built into webpack so you don’t need to install any additional packages.

Here’s a basic example of what it looks like:

Button.module.css
.button {
  background: green;
  color: #fff;
  padding: 10px;
}

.error {
  background: red;
}
Button.jsx
import styles from './Button.module.css';

const Button = () => {
  const [error, setError] = useState(false); 

  return (
    <button className={`${styles.button} ${error && styles.error}`}>
      Click me
    </button>;
  );
}

Pros:

  • Encapsulates styles within components, preventing CSS leaks and conflicts.
  • Supports local and global styles.
  • No need for additional libraries, as it is supported by popular bundlers like Webpack.

Cons:

  • Requires you to write styles in separate files, which may be an inconvenience for some developers.
  • Does not have as many features as other popular CSS-in-JS solutions.

Vanilla Extract

Vanilla Extract is a CSS-in-JS library that offers type safety, good theming support, and plenty of extensions. It’s an exciting alternative to existing solutions like Styled Components and Emotion. Vanilla Extract leverages TypeScript to provide type safety and generates CSS at build time, offering improved performance compared to other CSS-in-JS libraries.

import { createTheme, style } from '@vanilla-extract/css';

export const [themeClass, vars] = createTheme({
  color: {
    brand: 'blue',
    white: '#fff'
  },
  space: {
    small: '4px',
    medium: '8px',
  }
});

export const hero = style({
  backgroundColor: vars.color.brandd,
  color: vars.color.white,
  padding: vars.space.large
});

Pros:

  • Zero-runtime cost, as the CSS is extracted at build time and doesn’t require any JavaScript to be executed during runtime.
  • Offers type safety, ensuring that your styles are free from errors and inconsistencies.
  • Provides a flexible theming system based on CSS variables.
  • Framework-agnostic, meaning it can be used in any project that supports class names

Cons:

  • Less mature and has a smaller community compared to Emotion and Styled Components.
  • Requires you to write styles in .css.ts files, which may be an inconvenience for some developers.

Theming is an integral part of Vanilla Extract. You can create a theme structure using createThemeContract, and then create multiple themes that adhere to the same structure

Styled System

Styled System is a library that provides a set of utility functions for styling components. It is not a CSS-in_JS library itself but more like a layer on top of one that aids in maintaining consistent theming and styling across your app.

Here’s a very basic example of how it looks, you can find a more robust
example here.

import styled from 'styled-components'
import { space, color } from 'styled-system'

const Box = styled.div`
  ${space}
  ${color}
`

function App() {
  return (
    <Box
      p={3}
      bg='blue'
      color='white'
    >
      Hello, Styled System!
    </Box>
  )
}

Pros:

  • Works with any CSS-in-JS library.
  • Offers a set of predefined styles that can be used to style components.
  • Provides a flexible theming system based on CSS variables.

Cons:

  • Requires a CSS-in-JS library to be installed.
  • Putting CSS as props on components feels weird to me.
  • Can get verbose and bulky.

Styled Components

Styled Components is a popular library that allows you to create and use components as a low-level styling construct. It’s been around for a minute and has a large community behind it.

Here’s a basic example from their docs:

// Create a Title component that'll render an <h1> tag with some styles
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

// Use Title and Wrapper like any other React component – except they're styled!
render(
  <Wrapper>
    <Title>
      Hello World!
    </Title>
  </Wrapper>
);

Pros:

  • Encapsulates styles within components, preventing CSS leaks and conflicts
  • Easy to do dynamic styling based on props
  • Popular and actively maintained

Cons:

  • Can be slower than traditional stylesheets due to the lack of caching
  • Can become difficult to maintain as your app grows

styled-jsx

Created by the folks at Vercel, styled-jsx is a full, scoped and component-friendly CSS support library for JSX.

Here’s what it looks like:

export default () => (
  <div className="wrapper">
    <h1>Hello Styled-JSX</h1>
    <style jsx>{`
      .wrapper {
        background: #3f51b5;
        padding: 60px 30px;
      }
      h1 {
        color: #ff9800;
      }
    `}</style>
  </div>
)

Pros:

  • High-performance runtime-CSS-injection
  • Full CSS support, no tradeoffs
  • Built-in CSS vendor prefixing
  • If you’re using Next.js it’s automatically configured

Cons:

  • Limited features compared to other CSS-in-JS solutions

Theme UI

Theme UI is a library for creating themeable user interfaces based on constraint-based design principles.

Here’s an example from their docs, first you create your theme:

import type { Theme } from 'theme-ui'

export const theme: Theme = {
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
    monospace: 'Menlo, monospace',
  },
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#33e',
  },
}

Then you can style your UI:

/** @jsxImportSource theme-ui */
import { ThemeUIProvider } from 'theme-ui'
import { theme } from './theme'

export const App = () => (
  <ThemeUIProvider theme={theme}>
    <h1
      sx={{
        color: 'primary',
        fontFamily: 'heading',
      }}
    >
      Hello
    </h1>
  </ThemeUIProvider>
)

You can also use the sx prop to style any component:

/** @jsxImportSource theme-ui */

export default (props) => (
  <div
    sx={{
      fontWeight: 'bold',
      fontSize: 4, // picks up value from `theme.fontSizes[4]`
      color: 'primary', // picks up value from `theme.colors.primary`
    }}
  >
    Hello
  </div>
)

Pros:

  • Consistency and scalability
  • Ability to easily switch themes
  • Can style with or without creating components

Cons:

  • Less mature and has a smaller community compared to other libraries like Emotion and Styled Components.

Emotion

Emotion is a flexible and high-performance CSS-in-JS library that supports both string and object styles.

Here’s how it looks:

import styled from '@emotion/styled';

const Button = styled.button`
  background-color: blue;
  color: white;
  padding: 10px;
`;

function App() {
  return <Button>Click me</Button>;
}

export default App;

Pros:

  • Offers a powerful and flexible API for styling components
  • Smaller size and faster performance than some other CSS-in-JS alternatives
  • Has a strong community of users and a growing ecosystem of plugins and tools

Cons:

  • Can clutter the React Dev Tools as it creates a lot of wrapper components

There are many more options out there but I just wanted to touch on some of the top from the State of CSS survey. After using a handful of these and reading through the docs I realized they are all trying to solve the same problem but in slightly different ways. At the end of the day it all comes down to personal preference and your projects needs.

Edit on Github
More Posts