DEV Community

Cover image for CSS in JS (part 2)
Matt Thorning
Matt Thorning

Posted on • Originally published at hellocode.dev on

CSS in JS (part 2)

In my last blog post I wrote about how I had started using Emotion instead of Sass. Initially I was using Emotion's framework-agnostic package, but I have now switched to using the version which they created specifically for use with React because it adds some extra functionality which I liked the look of! This post is going to be about using Emotion with React, because of that, I'm going to be making the assumption that if you're reading this then you already know how to use React.

Let's get started, you can install the package with NPM:

npm install @emotion/core --save-dev

Then you will need to make some changes to your Babel setting to enable your project to work with this package.

Babel

There are two methods available for making your app work with @emotion/core. The quickest way is to import Emotion's jsx function at the top of your file and include a JSX Pragma on the line above it like so:

/** @jsx jsx */
import { jsx } from '@emotion/core'

The pragma (which is the bit in comments) tells Babel to use Emotion's jsx function in place of React's createElement. With these lines in your imports you will be able to use the css prop anywhere in your file that you may have previously used className. This is a good solution if you just want to try it out or if you don't have access to your Babel settings, it is a bit of a pain having to do this in every file though so if possible I recommend using the second method which is to add the Babel preset. This is easy to do, just install it with NPM and then include it in your .babelrc or babel.config.js:

{
  "presets": ["@emotion/babel-preset-css-prop"]
}

If you're using Gatsby (as I am for this site) there is a plugin you can use instead.

Once you've set up the Babel preset you no longer need to include the pragma or import the jsx function at the top of your files, every component in you codebase will now be able to use the css prop.

The css prop

The css prop accepts your styles in several ways which gives you some added flexibility when writing your components.

It will accept object styles:

const styles = {
  color: 'red',
  marginTop: '10px'
}

export default function HelloCode(props) {
  return <h1 css={styles}>Hello Code</h1>
}

It will also accept string styles which have been created with @emotion/core's css function:

import { css } from '@emotion/core'

const styles = css`
  color: red;
  margin-top: 10px;
`
export default function HelloCode(props) {
  return <h1 css={styles}>Hello Code</h1>
}

Unlike with the css function in the framework-agnostic version, this css function does not return a computed class name string; it returns an object which Emotion understands 'at a low level' and as such can be composed with other Emotion based styles. Therefore, it is important that you don't try to use the css function which is exported by the framework-agnostic version of Emotion.

You can also pass an array of styles to the css prop. The styles are applied left-to-right with any duplicated rules being overridden:

import { css } from '@emotion/core'

const blue = css`
  color: blue;
  margin-top: 10px;
`
const red = css`
  color: red;
`
export default function HelloCode(props) {
  return <h1 css={[blue, red]}>Hello Code</h1>
}
//Text will be red

This is great for controlling your styling through the logic of your component using its state or props. In the example below I have used a reducer with React's useReducer and useEffect hooks to add a class to the component five seconds after the component mounts:

import React, { useEffect, useReducer } from 'react'
import { baseStyle, animationStyle } from './style'

export default function HelloCode() {

  const reducer = (state, action) => {
    switch (action.type) {
      case 'addClass':
        return {
          style: [...state.style, action.payload],
        }
      default:
        return state
    }
  }

  const initialState = {
    style: [baseStyle]
  }

  const [state, dispatch] = useReducer(reducer, initialState)

  function animate() {
    setTimeout(() => {
      dispatch({
        type: 'addClass',
        payload: animationStyle,
      })
    }, 5000)
  }

  useEffect(() => {
    animate()
  }, [])

  return <h1 css={state.style}>Hello Code</h1>
}

So you can see that useEffect calls the animate function on component mount. This function uses setTimeout to dispatch the addClass action after a 5000 milliseconds delay. The payload of the action contains the reference to the animationStyle which is imported at the top of the file. The reducer matches the action type with addClass and updates the component's state with a new array containing both state.style and animationStyle from action.payload.

The Global component

I generally believe it's a good idea to keep your CSS styles scoped to your components. When writing React applications you build up from the smallest level of granularity; creating one large stylesheet which controls styling for the entire app flies in the face of this methodolgy (in my opinion). However, it's still important to maintain consistency in the way the UI looks across your application. There are two options available to help with this, here is the first.

@emotion/core provides a Global component which can be utilised to apply CSS rules globally to your site. It's used like this:

import { Global, css } from '@emotion/core'

export default function HelloCode() {
  return (
    <div>
      <Global
        styles={css`
          h1 {
            color: red;
          }
        `}
      />
      <h1>Hello Code</h1>
    </div>
  )
}

This is interesting because you can completely change the styling of your site just by rendering a Global component. One of React's strengths is that it makes conditionally rendering child components effortless, using the Global component as a child you could very easily change the entire look of your site with the push of a button:

import React, { useState } from 'react'
import { Global, css } from '@emotion/core'

const nightModeStyle = css`
  body {
    background: black;
  }
  h1 {
    color: white;
  }
`
export default function HelloCode() {
  const [nightMode, setNightMode] = useState(false)

  return (
    <div>
      {nightMode && <Global styles={nightModeStyle} />}
      <button onClick={() => setNightMode(!nightMode)}>Night Mode</button>
      <h1>Hello Code</h1>
    </div>
  )
}

Slight disclaimer with this one, I haven't tried it (yet); if you happen to try it and it doesn't work then let me know!

Theming

With theming you can create an object containing settings that you want to have access to in the css prop of any component. This is great for maintaining a consistent look and makes changing colours for branding a lot easier because you only need to change the values in one place instead of in every component.

If you want to use theming with Emotion then you wil first need to install it with NPM:

npm install emotion-theming --save-dev

emotion-theming uses React's context API, so first you need to wrap all of the components which you want to have access to the theme in the ThemeProvider. This is standard context stuff, only instead of a value prop it uses theme:

import { render } from 'react-dom'
import App from './app'
import { ThemeProvider } from 'emotion-theming'

const theme = {
  primaryTextColor: 'red'
  h1TopMargin: '10px'
}

render(
  <ThemeProvider theme={theme}>
    <App />
  </ThemeProvider>,
  document.getElementById('root')
)

Now, let's take an example from earlier on but this time we'll get the values from the theme:

import { css } from '@emotion/core'

const styles = theme => css`
  color: ${theme.primaryTextColor};
  margin-top: ${theme.h1TopMargin};
`
export default function HelloCode(props) {
  return <h1 css={styles}>Hello Code</h1>
}

The css prop also accepts a function which is called with the theme as an argument. When React sees this it walks back up the components tree until it finds a ThemeProvider and gets the theme object from its prop. Because this is a JavaScript object it can be passed around as you would any other object. Here's another example from earlier:

import { css } from '@emotion/core'

const blue = theme => css`
  color: blue;
  margin-top: ${theme.h1TopMargin};
`
const red = theme => css`
  color: ${theme.primaryTextColor};
`
export default function HelloCode(props) {
  return (
    <h1 css={theme => (
      [blue, red].map(style => style(theme))
    )}>
      Hello Code
    </h1>
  )
}
//Text will be red

Here we are passing the theme object to each function in the array using map. I'm sure you can see how powerful this can be; any css prop can very easily be turned into a function with access to everything in your theme object just by prepending it with theme =>!


That's covered everything I've been doing with Emotion lately. There's more in their documentation which I haven't covered but I've talked about the stuff which I've found the most useful. Still, take a look and if you see or know of anything that I've not mentioned which you think is useful then please let me know. Cheers! 👍

Top comments (2)

Collapse
 
mohamedbenoba profile image
Mohamed_Benoba

Thank you for these information

Collapse
 
thorning_m profile image
Matt Thorning

You're welcome, I hope it was useful to you.