CSS-in-JS

Jan. 25, 2021

Introduction

CSS-in-JS. A phrase which splits opinion in two nearly as much as how delicious cilantro is.

I will present a grab bag of opinions on why you might want to consider using it and share with you some reasons why I like using it. At the end I will make note of some recent reflections.

This article mostly considers React web apps using Emotion 10.

CSS-in-JS

CSS-in-JS is just that. Instead of writing rule ridden CSS stylesheets we now write JavaScript functions which return us styled components. CSS modules allowed us to co-locate our styles with our components but now we can go a step further and write our styles completely inline with our components. This benefits code organisation. We are also scoping our styles to our components, instead of polluting a global scope.If we delete our component, the styles are also removed. No more useless styles hanging out after midnight and bloating our precious bundle size for first render.

The new ‘in’ thing

It’s just JavaScript. It’s easy to define constant variables and share them amongst files. It could be a colour, it could be a margin, heck it could even be a blinking animation you want to use on every single clickable element because you miss <blink> so much.

It’s a great developer experience. One doesn’t have to switch back and forth between just to change a style. It’s all there.

What’s in a name?

The book industry is filled with shelves upon shelves of ideas for baby names. Naming is hard. CSS naming is no exception. The faster I am able to quickly write a component without worrying about a classname css selector the more I enjoy it.

The most basic, albeit terrible, way we can style is using inline styles and therefore avoid writing class names completely:

<h1 style={{fontWeight: 'bold', fontSize: '22px'}}>Inline Heading</h1>

Using this approach we’re constrained to this awful halfway mark between an object and CSS properties. Remembering to camel case properties and use strings for number values when our head is in CSS world constantly trips me up. CSS-in-JS provides a more natural inline style for us and it even comes with syntax highlighting in VSCode!

<h1 css={css`font-weight: bold; font-size: 22px`}>2nd Heading</h1>

Extending

This works great for small styles or specific overrides but what if we have a lot of styling? For example a large button component. With Emotion we can created a styled component like so:

const DefaultButton = styled(Button)`
  transition: all 0.25s;
  display: flex;
  align-items: center;
  justify-content: center;
  outline: none;
  box-shadow: none;
  border: none;
  border-radius: 100px;
  font-weight: 600;
  text-transform: uppercase;
  border-style: solid;
`;

From here we can create more specialised buttons by using the styled function again or by combining it with Emotion’s css function:

const FormButton = styled(DefaultButton)`
  text-transform: none;
  font-weight: 500;
  border-raduis: 0
`;

Once you ensure your react component can receive a className prop correctly to its children, you can use styled on your component:

const PrettyCoolComponent = ({children, className, ...props}) =>
  <div className={className}>{children}</div>

Emotional Rescue

As projects grow larger there is a concern for unused code and dangling styles. For example

// components/ui/common/typography.js

<h1 className='heading article-masthead'></br>
Sufficintly Interesting Heading
</h1>

Then in some long forgotten file /styles/og-styles-copy.css:

.heading {
    font-weight: bold;
}

.article-masthead {
    font-size: 24px
    color: #red;
    border-bottom: 1px solid grey;
    padding: 20px;
}

A useful aspect of using Emotion is our styling can be colocated with ease either by inlining our styles or colocating styled components. When you delete a components file, the styles go with it. No more orphaned styles.

Props to Emotion

All approaches using Emotion can incorporate our components props in anyway we see fit. After all it’s JavaScript. There is no need to toggle classnames for interaction styles, or any other dynamic styling you may want. Simply use props:

const MenuItem = stlyed(Item)`
  color: ${({active}) => active ? 'blue' : 'white'}`

Our MenuItem color will be blue if it’s active, or white if it’s not. A contrived example, but you can extrapolate on how powerful this flexibility may be.

Theme Player

My favourite aspect to CSS-in-JS is easy theming. The most used case for this online seems to be having a “dark mode” for websites that a user can toggle. I’ve found it can be useful in component resuability specifically in documentation sites, where all the structure of a project needs to be reused and only certain branding colours and styles changed.

const brandA = {
  primary: '#f8f9fa',
}

const brandB = {
  primary: 'green'
}

const BrandTitle = styled('h1')`
  font-size: 30px
  color: ${props => props.theme.primary}
`
<ThemeProvider theme={brandA}>
  <BrandTitle>
    Brand A
  </BrandTitle>
</ThemeProvider>

Or more concretely following on from our styled button example:


const StyledButton = styled<(Button)`
  transition: all 0.25s;
  display: flex;
  align-items: center;
  justify-content: center;
  outline: none;
  box-shadow: none;
  border: none;
  border-radius: 100px;
  font-weight: 600;
  text-transform: uppercase;
  border-style: solid;

  border-width: ${({ theme, size }) => theme.button.borderWeights[size]};
  padding: ${({ theme, size }) => theme.button.paddings[size]};
  color: ${({ theme, variant }) => theme.button.textColors[variant].default};
  cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
  font-size: ${({ theme, size }) => theme.button.fontSizes[size]};
  background-color: ${({ theme, variant }) => theme.button.colors[variant].default};
  border-color: ${({ theme, variant }) => theme.button.borderColors[variant].default};

  &:focus {
    background-color: ${({ theme, variant }) => theme.button.colors[variant].focus};
  }

  &:hover {
    background-color: ${({ theme, variant }) => theme.button.colors[variant].hover};
    border-color: ${({ theme, variant }) => theme.button.borderColors[variant].hover};
  }

  &:active {
    background-color: ${({ theme, variant }) => theme.button.colors[variant].active};
    border-color: ${({ theme, variant }) => theme.button.borderColors[variant].active};
  }

  &:disabled {
    background-color: ${({ theme, variant }) => theme.button.colors[variant].disabled};
    border-color: ${({ theme, variant }) => theme.button.borderColors[variant].disabled};
    color: ${({ theme, variant }) => theme.button.textColors[variant].disabled};
  }
`;

You can see from this example how we can change a variable in our theme file and have it affect our button styles.

Where do we go now?

I’ve been using Emotion for over 2 years. It’s developer experience is top notch, removing a lot of obstacles to just getting code running and a ticket complete. This is, however, a double edged sword. I enjoy not thinking about classnames and writing styles inline but I’ve also found myself not reusing a lot of styles.

Using a library like this brings with it tooling considerations. Integrating with Babel, or Webpack, or using custom pragma annotations are extra considerations. Additionally, in contrast to CSS preprocessor tooling, you’re also shipping a library which can impact performance.

There are aspects of Emotion I don’t fully understand which are also worth considering. It’s not zero runtime (unlike a library like Linaria) so there will be performance considerations.

Be aware of your team. It’s far easier for a designer or anyone else to read some basic CSS than to trawl through intimidating components.

SASS with CSS modules will get you a very long way. Stay tuned for more.

Ciarán Schütte, Software Developer