Skip to content

Files

Latest commit

 

History

History
 
 

next-emotion

twin, next, emotion

Download this example using degit:

npx degit https://github.com/ben-rogerson/twin.examples/next-emotion folder-name

Or keep scrolling for installation instructions.

Table of contents

Getting started

Installation

Install Next.js

npx create-next-app

Install the dependencies

npm install @emotion/react @emotion/styled @emotion/css @emotion/server
npm install -D twin.macro tailwindcss @emotion/babel-plugin babel-plugin-macros
Install with Yarn
yarn create next-app

Install the dependencies

yarn add @emotion/react @emotion/styled @emotion/css @emotion/server
yarn add -D twin.macro tailwindcss @emotion/babel-plugin babel-plugin-macros

Add the global styles

Twin uses the same preflight base styles as Tailwind to smooth over cross-browser inconsistencies.

The GlobalStyles import adds these base styles along with some @keyframes for the animation classes and some global css that makes the ring classes and box-shadows work.

You can add Twin’s GlobalStyles import in pages/_app.js:

// page/_app.js
import { GlobalStyles } from 'twin.macro'

const App = ({ Component, pageProps }) => (
  <div>
    <GlobalStyles />
    <Component {...pageProps} />
  </div>
)

export default App

Extract styling on server (optional)

If your notice your page flickering on first render, this might fix the problem. Creating a _document.js file like this will put critical styles in the head of the page.

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { extractCritical } from '@emotion/server'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    const page = await ctx.renderPage()
    const styles = extractCritical(page.html)
    return { ...initialProps, ...page, ...styles }
  }

  render() {
    return (
      <Html lang="en">
        <Head>
          <style
            data-emotion-css={this.props.ids.join(' ')}
            dangerouslySetInnerHTML={{ __html: this.props.css }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

Add the twin config (optional)

Twin’s config can be added in a couple of different files.

a) Either in babel-plugin-macros.config.js:

// babel-plugin-macros.config.js
module.exports = {
  twin: {
    preset: 'emotion',
  },
}

b) Or in package.json:

// package.json
"babelMacros": {
  "twin": {
    "preset": "emotion"
  }
},

Note: The preset gets set to 'emotion' by default, so adding the config is only useful if you want to adjust Twin’s other options.

Add the babel config

Add this babel configuration in .babelrc.js:

// .babelrc.js
module.exports = {
  presets: [
    [
      'next/babel',
      {
        'preset-react': {
          runtime: 'automatic',
          importSource: '@emotion/react',
        },
      },
    ],
  ],
  plugins: ['@emotion/babel-plugin', 'babel-plugin-macros'],
}

Add the next config

Add this next configuration in next.config.js if you aren’t using Webpack 5 yet:

// next.config.js
module.exports = {
  webpack: (config, { isServer }) => {
    if (!isServer) {
      // Unset client-side javascript that only works server-side
      // https://github.com/vercel/next.js/issues/7755#issuecomment-508633125
      config.node = { fs: 'empty', module: 'empty' }
    }

    return config
  },
}
Webpack 5 config

The API changed slightly in Webpack 5, so use this config instead:

// next.config.js
module.exports = {
  future: { webpack5: true }, // Use webpack 5
  webpack: (config, { isServer }) => {
    if (!isServer) {
      // Unset client-side javascript that only works server-side
      // https://github.com/vercel/next.js/issues/7755#issuecomment-508633125
      config.resolve = {
        fallback: { fs: 'empty', module: 'empty' },
      }
    }

    return config
  },
}

'fs' is a server-side dependency which we don’t want added client-side. Adding the code above will make sure we don’t experience errors.

Complete the TypeScript setup

If you’re using TypeScript, you’ll need to add the remaining types for your chosen css-in-js framework.

Setup instructions

First up, you’ll need to install some types for React:

npm install -D @types/react
// or
yarn add @types/react -D

Then twin needs some type declarations added for your chosen css-in-js library, otherwise you’ll see errors like this:

Module '"../node_modules/twin.macro/types"' has no exported member 'styled'.
// or
Module '"../node_modules/twin.macro/types"' has no exported member 'css'.
// or
Property 'css' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.

To fix this, create a twin.d.ts file in your project root (src/twin.d.ts with create-react-app) and add these declarations:

// twin.d.ts
import 'twin.macro'
import styledImport from '@emotion/styled'
import { css as cssImport } from '@emotion/react'

declare module 'twin.macro' {
  // The styled and css imports
  const styled: typeof styledImport
  const css: typeof cssImport
}

Then add the following to your typescript config:

// tsconfig.json
{
  "compilerOptions": {
    "jsxImportSource": "@emotion/react" // for the css prop
  },
  "files": ["twin.d.ts"],
  // or "include": ["twin.d.ts"],
}

Now that you’ve added the definitions, you can use these imports:

import tw, { css, styled, theme } from 'twin.macro'

And these props:

<div tw="">
<div css={}>

Customization

Twin options

Name Type Default Description
config string "tailwind.config.js" The path to your Tailwind config
preset string "emotion" The css-in-js library behind the scenes - also supports 'styled-components' and 'goober'.
hasSuggestions boolean true Display suggestions when a class isn’t found
dataTwProp boolean/string true Add a prop to your elements in development so you can see the original tailwind classes, eg: <div data-tw="bg-black" />, add all to keep the prop in production
debugPlugins boolean false Display generated class information in your terminal from your plugins
debug boolean false Display information in your terminal about the Tailwind class conversions
disableColorVariables boolean false Disable css variables in colors (not gradients) to help support IE11/react native
includeClassNames boolean false Look in className props for tailwind classes to convert
dataCsProp boolean true Add a prop to your elements in development so you can see the original cs prop classes, eg: <div data-cs="maxWidth[1em]" />
disableCsProp boolean false Disable twin from reading values specified in the cs prop.

Tailwind config

For style customizations, add a tailwind.config.js in your project root.

It’s important to know that you don’t need a tailwind.config.js to use Twin. You already have access to every class with every variant. Unlike Tailwind, twin.macro only generates styles for the classes so you don’t need to use PurgeCSS.

Choose from one of the following configs:

  • a) Start with an empty config:

    // tailwind.config.js
    module.exports = {
      theme: {
        extend: {
          colors: {},
        },
      },
      plugins: [],
    }
  • b) Start with a full config:

    # cd into your project folder then:
    npx tailwindcss-cli@latest init --full

    In the config, twin only reads the darkMode, theme and plugins entries, so feel free to remove the rest.

Plugins

External

You can use many Tailwind plugins with twin, like tailwindcss-typography and @tailwindcss/forms but there’s no compatibility with plugins that use the addVariant functions.

See list of supported plugins →

Custom classes

You can add your own custom css within a plugin. Here’s an example of a custom class that adds breakpoint based paddings from theme values:

// tailwind.config.js
module.exports = {
  // ...
  plugins: [paddings],
}

function paddings({ addComponents, theme }) {
  addComponents({
    '.my-padding': {
      '@screen md': {
        'padding-left': theme`padding.3`,
        'padding-right': theme`padding.3`,
      },
      '@screen lg': {
        'padding-left': theme`padding.6`,
        'padding-right': theme`padding.6`,
      },
    },
  })
}

Usage

Twin has a couple of different styling techniques to choose from.

Styled props

Use Twin’s tw prop when you have no conditional styles:

import 'twin.macro'

const Input = () => <input tw="border hover:border-black" />

Nest Twin’s tw import within a css prop to add conditional styles:

import tw from 'twin.macro'

const stylesInput = ({ hasHover }) => [
  tw`border`, // Add base styles first
  hasHover && tw`hover:border-black`, // Then conditional styles
]

const Input = props => <input css={stylesInput(props)} />

Your can add both tw and css props on the same element:

import tw from 'twin.macro'

const Input = ({ hasHover }) => (
  <input tw="border" css={[hasHover && tw`hover:border-black`]} />
)

Or mix sass and tw styles with the css import:

import tw, { css } from 'twin.macro'

const hoverStyles = css`
  &:hover {
    ${tw`text-black`}
  }
`

const stylesInput = ({ hasHover }) => [
    tw`border` // Add base styles first,
    hasHover && hoverStyles // Then conditional styles
]

const Input = props => <input css={stylesInput(props)} />

Tip: Prefer booleans over ternaries to reduce your line length and improve scannability.

Styled components

You can also use the tw import to create and style new components:

import tw from 'twin.macro'

const Input = tw.input`border hover:border-black`

And clone and style existing components:

const PurpleInput = tw(Input)`border-purple-500`

Then switch to the styled import to add conditional styling:

import tw, { styled, css } from 'twin.macro'

const stylesWidth = css`border: 1px solid hotpink`,

const Input = styled.input(({ hasHover }) => [
    tw`border rounded`, // Add base styles first
    hasHover && tw`hover:border-black`, // Then conditional styles
    !hasHover && stylesWidth // Then any css/sass in variables
])

const Component = () => <Input hasHover />

Next steps

Learn more about emotion

View more emotion examples