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.
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.
So why exactly is Emotion recommending this for React devs?
The main three advertised benefits of using @emotion/core
are server-side rendering (SSR), theming, and customizability. Let's dig into those.
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.
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:
yarn add emotion-server
_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. ā ļøFor completeness, here's some instructions for Gatsby users, too.
yarn add gatsby-plugin-emotion
'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.
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;
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.
css
propEmotion 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!
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.
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.
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>
);
};
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