Emotion is my favorite CSS-in-JS library.
It's easy to define style classes (both inline or in separate files), compose them in powerful ways with the cx
utility, sprinkle them into React components using the standard className
attribute. There's no need to ever modify your markup/JSX to apply styles (as it should be!). Plus you only need to install a single module (yarn add emotion
) and there's no complicated Babel plugin or config file to set up.
I'm currently building a Tailwind-style CSS-in-JS utility styling library (join my newsletter at the bottom of this page to stay updated!) and Emotion provides the perfect layer of abstraction to build upon.
Unfortunately everything I just said only applies to the vanilla emotion
module (https://emotion.sh/docs/emotion), not the inexplicably-named @emotion/core
module. @emotion/core
is the React-focused Emotion wrapper that gives you some extra goodies, like server-side rendering and theming.
To install @emotion/core
, you need to install the @emotion/babel-preset-css-prop
Babel plugin and add it to your .babelrc
. If you're using Create React App, this is simply impossible. If you're using TypeScript, you may not have seen a .babelrc
file in years.
If you're in one of those buckets, there's a fallback solution! Just copy these two lines at the top of every React component you want to style with Emotion:
/** @jsx jsx */
import { jsx } from '@emotion/core';
Yes, you have to import jsx
from @emotion/core
even if you don't use it. 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 hundreds of sad, frustrated TypeScript fans complain about this over the last 18 months.
Emotion also recommend using their non-standard css
prop to style your components, instead of React's built-in className
. And as someone who's building a styling library on top of Emotion, I hate that this is the officially recommended approach for React projects.
To implement this custom css
prop requires Emotion to entirely hijack React's JSX parsing (), which seems messy and overkill.
The usage of a non-native css
prop kneecaps the portability/extensibility of your component; it's now unusable in any codebase that isn't configured to use @emotion/core
. Bad!
If you want to stick with using the className
prop or combine Emotion classes with cx
(the Emotion equivalent of the classnames
utility), you need to add the ClassNames
component + render prop as the root of every component you want to style (docs). Now your styling library is forcing you to restructure your markup. Just say no! Also: render props?! What is this, 2018?
As far as I understand the only benefits of using @emotion/core
over emotion
are "out-of-the box server-side rendering" and built-in support for theming. Let's break those down.
I appreciate the technical difficulty of getting SSR to work out of the box; it's a hard problem. I don't appreciate that Emotion tries to drive people towards @emotion/core
instead of tellling them how insanely easy it is to set up SSR themselves with vanilla emotion
.
Emotion provides no concrete guidance on how to do this, just a link to a sample Next.js project (this one). Notably, this project a) has the internet's least informative 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 how to do it.
yarn add emotion-server
- create
_document.tsx
in yourpages
directory and copy this gist into it - ok ur done
yarn add gatsby-plugin-emotion
- add
gatsby-plugin-emotion
to yourplugins
list ingatsby-config.js
Touting this "feature" is preposterous. The point of CSS-in-JS is to define our styles as a data structure in a high-level programming language. Your styles are code; you can use the full might of JavaScript to define/modify/generate your styles; that's the whole appeal That means you can define your theme properties as variables and import them wherever you need them.
If you're using TypeScript here's some code to get you started:
// theme.ts
export const primaryColor = "blue";
export const secondaryColor = "red";
export const serif = `"Merriweather", 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;
Perhaps there are lots of great reasons to use @emotion/core
that I'm not seeing and aren't in the Emotion docs. If there are, yell at me on Twitter here: https://twitter.com/vriad! I've been wrong before, or so I'm told.
But in the meantime, I'll continue to be flabbergasted that the Emotion team decided to put all their eggs in a basket that, imho, is strictly worse than their excellent original solution. I'll also be incredibly thankful and indebted to them for building the true "core" emotion
package, which is a glorious achievement and a huge win for developers!
Say to no styling libraries that require Babel plugins, JSX hijacking, markup modification, and Babel configuration to work. There are better ways!