DEV Community

Ondrej Sevcik
Ondrej Sevcik

Posted on • Originally published at ondrejsevcik.com

Improving the performance of styled components with native CSS features

Styled components are slow. But can we improve its performance by using some of the native CSS features? I've decided to test it out.

The problem

The problem with styled components is that they recalculate styles based on runtime values. Every time the component is rendered, it needs to build up the stylesheet and insert it into the document. This is not an issue in small apps, but in larger applications, it affects performance.

What you want to avoid is dynamic components (a component that depends on theme or props).

Before a dynamic component is inserted into DOM, it needs to parse the template → generate CSS → preprocess styles → inject it into DOM. Static components on the other hand can skip some of these steps and are thus much faster.

Exploring alternatives

Given that, I’ve started exploring how could we rewrite dynamic components into static ones.

Here is a classic example of a dynamic styled component.

const Box = styled.div`
  padding: ${props => props.padding};
`

<Box padding="1rem">I'm a box</Box>
Enter fullscreen mode Exit fullscreen mode

This could be easily rewritten as a static component with a help of CSS Variables.

const Box = styled.div`
  font-size: var(--padding);
`

<Box style={{--padding: '1rem'}}>I'm a box</Box>
Enter fullscreen mode Exit fullscreen mode

But usually, in larger apps, we don’t use specific values directly, but rather design tokens from the design system.

Here’s a similar example, but this time with padding with three options.

const Box = styled.div<{padding: 'small' | 'medium' | 'large'}>`
  padding: ${props => {
      switch (props.color) {
        case "small":
          return "1rem";
        case "medium":
          return "2rem";
        case "large":
          return "3rem";
      }
    }};
`

<Box padding="medium">I'm a box</Box>
Enter fullscreen mode Exit fullscreen mode

In this case, we can’t really use CSS variables. But we could use data attributes instead.

const Box = styled.div<{'data-padding': 'small' | 'medium' | 'large'}>`
  &[data-padding="small"] {
    padding: 1rem;
  }
  &[data-padding="medium"] {
    padding: 2rem;
  }
  &[data-padding="large"] {
    padding: 3rem;
  }
`

<Box data-padding="medium">I'm a box</Box>
Enter fullscreen mode Exit fullscreen mode

This doesn’t look bad, and is much easier to read.

The only disadvantage of this approach is that it’s actually rendering the data-padding attribute into the DOM. Writing to DOM might affect the performance as well.

Performance

I’ve decided to test it out.

In this simple demo, I build a table with 1000 items in it using 3 different approaches. The first one is using dynamically styled-components, the second is using statically styled-components and the third one is using plain old CSS class names.

It turns out that using data-attributes is indeed a little bit faster than using dynamic props on styled-components. Not surprisingly the fastest was using CSS class names.

React Profiller results


Using native CSS features can indeed improve the performance of styled-components. It makes the styles also more readable. Rather than writing CSS inside JS function inside CSS declaration inside JS file, you can just write CSS that everybody understands.

But using CSS in JS is still controversial. It improves DX while hurting the user experience.

As long as CSS in JS doesn't improve UX & DX at the same time, it's better to stick with plain old CSS and a some good CSS methodology.

Top comments (4)

Collapse
 
machy8 profile image
Vladimír Macháček • Edited

I have tried the turbopack for Next.js when testing Stylify CSS CSS splitting, and it seems that it allows to import various CSS files (layout/page/components). Sadly this doesn't work in production build yet. Only dev for some reason.

However, if that feature works in Next and Turbopack, I guess there will be not much reason to use CSS-in-JS anymore (except for those, that people simply like this feature). Even those that brings minimum runtime or generate CSS on demand. It still is a runtime javascript for generating a CSS chunks.

Solid, Qwik, Astro, SvelteKit and other frameworks already has this feature and it works pretty fine.

Collapse
 
ondrejsevcik profile image
Ondrej Sevcik

I can see how Stylify could be used instead of Tailwind for simple styling, but I feel like it would get messy very easily for any more advanced layout.

Collapse
 
machy8 profile image
Vladimír Macháček

I was referring to CSS-in-JS optimization problems, that I have found during testing Stylify and CSS splitting. The link is there just for a click.

I simply agree with the info in the article and I hope we will be able to use pure CSS insteaf of CSS-in-JS one day, which will probably happen if Turbopack get's production ready.

Thread Thread
 
ondrejsevcik profile image
Ondrej Sevcik

Thanks for clarification. Now I understand your point.