Why you shouldn't use @emotion/core

Colin McDonnell @colinhacks

published June 6th, 2020

A maintainer of Emotion published a very thoughtful response to a version of this post! I encourage you to check that out here. The essay below has modified to address some of his points.

Emotion is my favorite CSS-in-JS library.

It's easy to define style classes ā€” both inline or in separate files. You can powerfully compose classes in powerful ways with the cx utility (the Emotion equivalent of Jed Watson's classnames). You sprinkle in your styles using the standard className attribute. There's no need to modify your markup/JSX ā€” as it should be! You only need to install a single module (yarn add emotion). And there's no complicated Babel plugin or config file to set up.

import { css, cs } from 'emotion';

const redBorder = css({ border: '1px solid red' });
const blueText = css({ color: 'blue' });

const MyComp = () => {
  return <div className={cx(redBorder, blueText)}>hello</div>;
};

I'm currently building a Tailwind-style CSS-in-JS utility styling library with a maniacal focus on brevity and developer experience. If you want to hear about that when it's ready, join my mailing list.

The "but"

But unfortunately everything I just said only applies to the "vanilla" emotion module (docs), not the confusingly-named @emotion/core module.

@emotion/core is the React-centric Emotion wrapper that gives you some extra goodies, specifically server-side rendering and theming. It's also the official recommendation of the Emotion project for any React project.

Emotion's recommending emotion core

So why exactly is Emotion recommending this for React devs?

Minimal upsides

The main three advertised benefits of using @emotion/core are server-side rendering (SSR), theming, and customizability. Let's dig into those.

Out-of-the-box SSR

There's no denying this is a remarkable technical achievement. Getting SSR to "just work" with both Next.js, Gatsby, and the classic ReactDOMServer.renderToString approach is very impressive. I'd be lying if I claimed to understand the complexities involved.

I don't have data on this, but ā€” in my experience ā€” SSR isn't a consideration for a hefty majority of React projects. If you started a project/website in the last 7 years where SEO/SEO/pageload speed/bundle size was an important design consideration, you probably didn't choose React. Website builders, static site generators, and HTML templating still dominate that arena. Take it from someone who got torn apart on HN for advocating the use of React/Next.js for personal developer websites šŸ˜˜

For the people who do need SSR, the guidance is a little slim.

Next.js

There's no explicit documentation from Next.js on how to set up SSR with vanilla emotion. Next.js provides a sample project here. Notably, this project a) has a very uninformative Readme and b) is built with @emotion/core! So it's not immediately obvious that the approaches on display will even transfer to a vanilla project.

Enough buildup. Here's the internet's first comprehensive guide to setting up SSR with vanilla Emotion and Next.js:

  1. yarn add emotion-server
  2. create _document.tsx in your pages directory and copy this gist into it. āš ļø This gist is intended for Emotion v10. Version 11 is the latest version. āš ļø
  3. ok ur done

Gatsby

For completeness, here's some instructions for Gatsby users, too.

  1. yarn add gatsby-plugin-emotion
  2. add 'gatsby-plugin-emotion' to your plugins list in gatsby-config.js

If you're using @emotion/core to avoid the complexities of SSR configuration, you may want to reconsider.

Theming

In the era of React Context and Hooks, there's no reason for libraries to use prop or high-order components to handle theming. Emotion provides a useTheme hook, but it still requires adding an additional library (emotion-theming).

This isn't a controversial claim; the next version of Emotion will explicitly recommend using a Context/Hook based solution, so I won't belabor this point.

Even Context/Hooks might be overkill for many projects. Just define your theme values as variables and import them into components as needed. If you're using TypeScript here's some code to get you started:

// theme.ts
export const primaryColor = "blue";
export const serif = `'Encode Sans', Times New Roman, Times, serif`;

// anydamnfile.ts
import { css } from 'emotion';
import * as theme from './theme.ts';

export const MyComponent = ()=>{
  return <p className={css({ color: theme.primaryColor, fontFamily: theme.serif })}>
}

If you want to import your theme with a useTheme hook, here's a workable implementation that took me several seconds to write:

import * as theme from './theme.ts';
export const useTheme = () => theme;

Customization

Edit: This section was added after the fact to address a concern raised by the maintainer of Emotion in his response to an earlier version of this post.

@emotion/core provides a CacheProvider component that lets you customize low-level aspects of it's behavior. This customization isn't possible with vanilla emotion. I'll let the maintainer of Emotion explain it:

Because @emotion/core doesn't give you string class names back, but rather just opaque objects, the rule insertion is lazy. This might not be as interesting on its own, but it allows us to customize injection through <CacheProvider>. Use cases for this are not as common - but they exist and once you ever come across one then you might be very thankful for the path we've chosen...Main things that can be customized:

  • prefixing rules
  • parser plugins
  • container element (very important for rendering into iframes)
  • nonce (very important for CSP)

ā€” Andarist (Github)

If you absolutely need this degree of customizability, then @emotion/core is probably right for you.

For everyone else, let's look at the downsides.

Serious downsides

The css prop

Emotion recommends using their non-standard css prop to style your components, instead of React's built-in className. This causes me immeasurable emotional pain.

This approach destroys the portability of your React components. Your components are now unusable in any codebase that isn't configured to use @emotion/core.

The portability and encapsulation of React components is one of the most powerful and wondrous achievements in web development in the last last decade. Don't give that up without a good reason!

Installation woes

Unfortunately to get that non-native css prop to work, Emotion core entirely replaces your project's JSX parser. It substitutes the built-in React.createElement function with Emotion's custom jsx function.

Replacing React's createElement function

There are a couple ways to set this up.

Option #1: install the @emotion/babel-preset-css-prop Babel plugin and add it to your .babelrc. If you're using Create React App, this isn't impossible. If you're using TypeScript, you probably don't have a .babelrc in your project.

If you're in one of those buckets, there's option #2: copy these two lines at the top of every React component you want to style with Emotion:

/** @jsx jsx */

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

If your TypeScript config or linter doesn't allow unused imports, you'll have to disable those rules to get rid of the warning. Check out this issue if you want to see dozens of TypeScript users being sad about this.

Sad Typescripters with linter warnings

Lack of composability

Perhaps the most damning issue with @emotion/core is that it makes the simple things harder. If you want to define a new class or use cx, you have to wrap your component with the ClassNames render prop. But with @emotion/core, these basic functions ā€” found in nearly all CSS-in-JS libraries ā€” requires you to modify your markup. In my humble opinion, requiring markup modifications is a cardinal sin for a styling library.

Here's the example from the top of this post, reimplemented with @emotion/core:

import { ClassNames } from '@emotion/core';

const MyComp = () => {
  return (
    <ClassNames>
      {({ css, cx }) => {
        const redBorder = css({ border: '1px solid red' });
        const blueText = css({ color: 'blue' });

        return <div className={cs(redBorder, blueText)}>hello</div>;
      }}
    </ClassNames>
  );
};

Wrapping up

I understand how this happened. Vanilla emotion was undoubtedly getting flooded with GitHub issues by frustrated developers hitting against subtle limitations of its design. @emotion/core fixes these issues. But because @emotion/core is now the officially recommended approach for all React projects (the vanilla option isn't even mentioned on the Readme anymore), I suspect thousands of developers using it who would be better served by plain ol' emotion.

And finally: a huge thank you to the Emotion team for all their exceptional work and contributions to the open-source community.

I'm an open sourcer and ride-or-die React fan. Put your email in the box to hear when I write new things.

Subscribe