banner
他山之石

他山之石

Using emotion in React and TypeScript

This article explains how to use the CSS in JS solution, emotion, in a React project.

image

emotion#

emotion is a high-performance and flexible CSS-in-JS library. It is framework-agnostic, meaning you can use it with Vue or React. Currently, we are using it with React, and it has been stable in production for multiple projects. emotion11 is a slight improvement over emotion10, focusing mainly on developer experience, TypeScript type improvements, and the use of a new parser: Stylis.
This article primarily focuses on integrating emotion11 in React and TypeScript.

Changes#

Package Renaming#

One of the most significant changes in emotion11 is the renaming of most user-facing packages.

List of renamed packages:

  • @emotion/core → @emotion/react
  • emotion → @emotion/css
  • emotion-theming → moved into @emotion/react
  • emotion-server → @emotion/server
  • create-emotion → @emotion/css/create-instance
  • create-emotion-server → @emotion/server/create-instance
  • babel-plugin-emotion → @emotion/babel-plugin
  • eslint-plugin-emotion → @emotion/eslint-plugin
    jest-emotion → @emotion/jest

Hooks#

Hooks are used internally to optimize the package size and provide a better DOM tree representation in React DevTools.

TypeScript#

TypeScript types have been completely rewritten.#
  1. Reduced build time when using emotion, especially in large projects.
  2. In many cases, there is no longer a need to manually specify generic parameters for emotion components.
  3. Improved support for union types as props, which should be correctly inferred.
  4. Restricted the css function to prevent passing invalid types.
  5. Changed the generic parameters for styled. If you specify ComponentType, you need to remove that generic parameter.
  6. The second parameter ExtraProps is no longer required for styled. Instead, it is moved after the styled call. Therefore, styled<typeof MyComponent, ExtraProps>(MyComponent) should be rewritten as styled(MyComponent)({}).
Theme Type#

Providing types for themes is now easier. You can create a built-in Theme interface like this, instead of creating a custom instance as before:

import '@emotion/react'

declare module '@emotion/react' {
  export interface Theme {
    primaryColor: string
    secondaryColor: string
  }
}
css prop Type#

The way emotion11 provides TypeScript support for the css prop has changed based on the JSX runtime used. Now, you can add support for the corresponding css prop only to components that support the className prop (because emotion's JSX factory function takes the provided css prop, parses it, and passes the generated className to the rendered component).

Stylis V4#

The css parser Stylis used by emotion has been upgraded. It fixes some long-standing parsing edge cases and becomes smaller and faster.

Emotion Caching#

When creating a custom instance with caching, the key option is now required. Make sure it is unique (and not equal to "css"), as it is used to link styles to the cache. If multiple caches share the same key, they may "fight" over each other's style elements. The new prepend option allows Emotion to add style tags at the beginning of the specified DOM container instead of the end.

Usage#

Installation#

yarn add @emotion/react

Usage#

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { jsx, css } from '@emotion/react'

const style = css`
  color: hotpink;
`

const SomeComponent = ({ children }) => (
  <div css={style}>
    Some hotpink text.
    {children}
  </div>
)

const anotherStyle = css({
  textDecoration: 'underline'
})

const AnotherComponent = () => (
  <div css={anotherStyle}>Some text with an underline.</div>
)

render(
  <SomeComponent>
    <AnotherComponent />
  </SomeComponent>
)

css prop#

The primary way to write styles with emotion is to use the css prop, which provides a concise and flexible API for styling components.

There are two ways to use the css prop:

Babel Preset#

The Babel preset automatically enables emotion's css prop when using the classic JSX runtime. If you want to use the new JSX runtime, do not use this preset and instead use @emotion/babel-plugin.

  • Installation
yarn add @emotion/babel-preset-css-prop
  • Usage

.babelrc

{
 "presets": [
   [
     "@emotion/babel-preset-css-prop",
     {
       "autoLabel": "dev-only",
       "labelFormat": "[local]"
     }
   ]
 ],
}

If you are using a compatible React version (>=16.14.0) and want to use the new JSX runtime, you can configure it as follows:

.babelrc

{
  "presets": [
    [
      "@babel/preset-react",
      { "runtime": "automatic", "importSource": "@emotion/react" }
    ]
  ],
  "plugins": ["@emotion/babel-plugin"]
}

JSX Pragma#

Set the jsx compilation directive at the top of the source file using the CSS prop. This option is best for testing the css prop functionality or for projects where you cannot configure the babel configuration (e.g., create-react-app, codesandbox, etc.).

/** @jsx jsx */

Similar to comments that include linter configurations, this configuration sets up the jsx babel plugin to use the jsx function instead of React.createElement.

If you are using a zero-configuration tool that automatically detects which runtime to use (classic or automatic) and you are already using a React version with the new JSX runtime (so the runtime is automatically configured for you with runtime: 'automatic'), such as Create React App 4, then the following compilation directive may not work, and you should use:

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

Using the css prop#

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

render(
  <div
    css={{
      backgroundColor: 'hotpink',
      '&:hover': {
        color: 'lightgreen'
      }
    }}
  >
    This has a hotpink background.
  </div>
)

Theme#

The Theme is included in the @emotion/react package.

Add the ThemeProvider at the top level of your application and use props.theme to access the theme in styled components or provide a function that accepts the theme as a css prop.

Usage#

css function#
import { ThemeProvider } from '@emotion/react'

const theme = {
  colors: {
    primary: 'hotpink'
  }
}

render(
  <ThemeProvider theme={theme}>
    <div css={theme => ({ color: theme.colors.primary })}>
      some other text
    </div>
  </ThemeProvider>
)
useTheme hook#
import { ThemeProvider, useTheme } from '@emotion/react'

const theme = {
  colors: {
    primary: 'hotpink'
  }
}

function SomeText (props) {
  const theme = useTheme()
  return (
    <div
      css={{ color: theme.colors.primary }}
      {...props}
    />
  )
}

render(
  <ThemeProvider theme={theme}>
    <SomeText>some text</SomeText>
  </ThemeProvider>
)

Types#

The types for the theme need to be declared in a types file; otherwise, TypeScript will throw errors.

types/emotion.d.ts

import "@emotion/react";

declare module "@emotion/react" {
  export interface Theme {
    colors: {
      layoutBodyBackground: string;
      headingColor: string;
      textColorSecondary: string;
      success: string;
      warning: string;
      error: string;
      primary: string;
      textColor: string;
    };
    fontSizes: {
      base: number;
    };
  }
}

tsconfig.json

{
  "compilerOptions": {
        "paths": {
          "*": [
            "types/*"
          ],
        },
    }
}

Summary#

CSS in JS solutions, including emotion, have several advantages over traditional approaches like less or sass. One significant advantage is the improved build speed and the absence of redundant CSS. However, it may take some time for those accustomed to traditional CSS to become familiar with this approach. In conclusion, it is worth giving it a try.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.