Why styled components are bad


Let’s say you open a file in an unknown project, or a project you worked on a long time ago (which is basically the same thing), and jump directly to the render method of a component that you need to work on. This is what you see:

function Component() {
  return (
    <Glorb>
      <Obul>
        <Zirk />
      </Obul>
    </Glorb>
  )
}

Now, assume this project uses styled-components. What are Glorb, Obul and Zirk:

  • Styled components that only add some styling?
  • Real components that encapsulate logic & behavior?

There is no way to know! It could very well be any of these possibilities:

/* just some cute styling :) */
export const Glorb = styled.div`
  border: 1px solid salmon;
`
export function Glorb({ children }) {
  /* lots of logic */
  useEffect(() => { /* ... */ }, [])
  /* and funky stuff */
  return (
    React.Children.map(children, child => /* ... */)
  )
}

The only solution left at this point for you is to jump to the definition of each of those components to try to make sense if any of them does something funky, before you work further on whatever you need to fix. In other words, you have no guarantees about what’s going on in that first snippet. You can’t make assumptions about what those components are.

Besides, the JSX tag name is not a natural place to encode styling. The tag name for elements has always been for the logic & behavior it encodes, be it a div, button, input or even custom ones like Combobox or DatePicker. You’re going to be able to tell from the name right away what behavior it encapsulates. So what is the best place to encode styling, you ask me? The same place that we’ve been using for decades: class(Name) and style.

Now imagine again you come in an unknown project and you look at this:

function Component() {
  return (
    <div className={glorb}>
      <div className={obul}>
        <div className={zirk} />
      </div>
    </div>
  )
}

Sure, it’s very slightly more verbose. On the other hand you know for a fact that all there is here are divs and nothing funky is going to happen. glorb, obul and zirk are classnames and you don’t need to jump anywhere to figure that out. If you later need to add styling via props, you’re already set up to clsx everything:

function Component({ className }) {
  return (
    <div className={clsx(glorb, className)}>
      { /* etc */ }
    </div>
  )
}
/*
 * Note how everything style is defined at a single point.
 * No need to glance at the tag to figure out if it adds styling as well,
 * you just know instantly it's all in the className.
 * Simple, predictable, consistent.
 */

Reusability

There’s also the fact that styles gone into a styled components are not reusable. Say you want to reuse and combine the styles of Glorb and Obul. How you do?

const Glorb = styled.div`color: red;`
const Obul = styled.div`background-color: yellow;`
function Component() {
  return (
    <Glorb,Obul /> // <!--- ??? :')
  )
}

If you wanted to make it work, you’d need to both define the styled components and expose the styles CSS string for others to re-use.

Whereas if you have classNames glorb and obul, you again simply clsx and you’re done. You can expose them & export them, and it works anywhere. Classes are native to the ecosystem and everyone understands them.

const glorb = css`color: red;`
const obul = css`background-color: yellow;`
 
function Component() {
  return (
    <div className={clsx(glorb, obul)} />
  )
}

Performance

If you’re using styled components, each of them is roughly going to be doing this:

function StyledButton({ className, ...rest }) {
  return (
    <button className={clsx(className, autogeneratedStyleClassName)} {...rest} />
  )
}

If you’re using styled components to style everything, then you’re roughly doubling the cost of rendering your whole app. Admittedly your logical components will be doing more work so it’s going to be a factor between 1 and 2, but it’s still a non-negligible cost on a framework that already has quite a few costs.

Final notes

Because I’m sure I’ll get comments about it, I’m of course not arguing that you shouldn’t encapsulate components if they form the base of your app’s design language. If you need to define commmon elements that are “style-only” but that still represent a different logical block, say Card vs Paragraph, then sure go ahead. When you open a render function with <Paragraph>...</Paragraph> and <Card>...</Card>, you’ll still know that nothing funky is going on because those become the common way to write p or div in your app and that’s fine. It doesn’t negates the fact that using styled-components everywhere is going to shit on the readability and reusability of your codebase. Styled components are a net negative in nearly all cases.

So please, stop doing this:

export const Glorb = styled.div`
  border: 1px solid salmon;
`

and start doing this:

export const glorb = css`
  border: 1px solid salmon;
`