Why?
Rather than utilizing class names and handling the optimization of which Tailwind classes to include we can leverage the style attributes but render those values with Emotion.
We can also integrate dynamic and existing styling with Emotion. Further more because the styles are dynamically assessed we can check that the style actually exists from Tailwind.
If there is ever a className
you're trying to use that isn't supplied by Tailwind, or a style that was created from your modifications to Tailwind config an error will be thrown.
This allows you to verify that all your styling and classes exist in your whole app.
Setup the Dependencies and Base
Rather than provide an install this is the package.json
there are a lot of packages that all work in conjunction with each other.
{
"name": "tailwindcss-emotion",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"build:base-css": "tailwindcss build ./styles/tailwind.base.css -o ./styles/base.css"
},
"dependencies": {
"@emotion/css": "^11.0.0-rc.0",
"@emotion/react": "^11.0.0-rc.0",
"@emotion/server": "^11.0.0-rc.0",
"@emotion/styled": "^11.0.0-rc.0",
"next": "latest",
"react": "^16.14.0",
"react-dom": "^16.14.0"
},
"devDependencies": {
"@babel/core": "^7.12.3",
"@emotion/babel-preset-css-prop": "^11.0.0-rc.0",
"@tailwindcss/ui": "^0.6.2",
"@tailwindcssinjs/macro": "^0.13.0",
"babel-plugin-macros": "2.8.0",
"tailwindcss": "^1.9.3"
},
"license": "MIT"
}
This is just a basic Tailwind config. Setup yours for whatever you need.
// tailwind.config.js
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
},
},
variants: {},
plugins: [require('@tailwindcss/ui')],
}
If you make any changes here run yarn build:base-css
to rebuild and regenerate the base css.
Config Babel
Now create a custom .babelrc
file at the root, and add next/babel
as the first preset. This is essential. Then add our @emotion/babel-preset-css-prop
to the preset which will automatically look for css={}
props and swap over the jsx
pragma for you.
So you do not need to manually add /** @jsx jsx */
to the top of the file with the jsx
import.
Also add babel-macros
so we can transform our Tailwind classnames into CSS that will be passed into Emotion.
// .babelrc
{
"presets": ["next/babel", "@emotion/babel-preset-css-prop"],
"plugins": ["macros"]
}
Add in Custom _document Page
This is semi-optional, but probably recommended. If you plan to use @emotion/css
at all then this it is required to setup a custom _document
page. This will allow you to render and extract all the styling at SSR time to ensure it's all there at initial render time.
Also to include font stylings that are recommended from Tailwind.
import Document, { Html, Head, Main, NextScript } from "next/document";
// Required for @emotion/css
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 }}
/>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Add in Custom _app Page
The custom _app
will allow us to include global CSS, but not have it imported as css modules. Next.js will automatically include and insert a link tag in the head for us.
import Head from "next/head";
import "../styles/base.css";
export default function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<title>Tailwindcss Emotion</title>
</Head>
<Component {...pageProps} />
</>
);
}
Style Option One with className
If you do not want to use the @emotion/babel-preset-css-prop
plugin this is an option. Using @emotion/css
and wrapping the tw
macro to turn the combination of stylings into a stringified className
.
Also if you need to combine styles you can use the cx
export from @emotion/css
to dynamically apply styling. This is the most verbose option of styling.
import tw from "@tailwindcssinjs/macro";
import { css, cx } from "@emotion/css";
const base = tw`relative flex justify-center w-64 min-w-full px-4 py-2 text-sm font-medium leading-5 text-white border border-transparent rounded-md `;
const styles = {
cssBase: css(base),
cssButton: css(tw`
bg-gray-600
hover:bg-gray-500
focus[outline-none border-gray-700 shadow-outline-gray]
active:bg-gray-700
transition duration-150 ease-in-out
`),
};
const Index = () => (
<div css={tw`grid items-center justify-center h-screen`}>
<button className={cx(styles.cssBase, styles.cssButton)}>
Emotion + Tailwind
</button>
</div>
);
export default Index;
Style with @emotion/styled
A typical styling from @emotion
is to use the styled
package and create styled components. These are components that can be used as normal React components but have their encapsulated styling applied.
You can additionally supply a className
or even css
prop to add extend and add more styling.
import tw from "@tailwindcssinjs/macro";
import styled from "@emotion/styled";
const Button = styled.button(tw`
bg-indigo-600
hover:bg-indigo-500
focus[outline-none border-indigo-700 shadow-outline-indigo]
active:bg-indigo-700
transition duration-150 ease-in-out
`);
const base = tw`relative flex justify-center w-64 min-w-full px-4 py-2 text-sm font-medium leading-5 text-white border border-transparent rounded-md `;
const Index = () => (
<div css={tw`grid items-center justify-center h-screen`}>
<Button css={base}>Emotion + Tailwind</Button>
</div>
);
export default Index;
Style with css
Prop
A handy method that @emotion/babel-preset-css-prop
allows is to add a css
prop. This means we do not need to import anything but our tailwind macro. It will handle converting our supplied styles to a className
. So it might appear as a css
prop here but will be compiled to className
prop.
Further we don't even need cx
here as it accepts an array
of styles to apply, and this can even be dynamically changed.
import tw from "@tailwindcssinjs/macro";
const base = tw`relative flex justify-center w-64 min-w-full px-4 py-2 text-sm font-medium leading-5 text-white border border-transparent rounded-md `;
const styles = {
base: bas,
button: tw`
bg-teal-600
hover:bg-teal-500
focus[outline-none border-teal-700 shadow-outline-teal]
active:bg-teal-700
transition duration-150 ease-in-out
`
};
const Index = () => (
<div css={tw`grid items-center justify-center h-screen`}>
<button css={[styles.base, styles.button]}>Emotion + Tailwind</button>
</div>
);
export default Index;
VSCode Extension
If you want an even easier time writing your styling install the VSCode extension https://marketplace.visualstudio.com/items?itemName=DennisVash.twin-macro-autocomplete-vscode
It will autocomplete for the tw
macro.
Conclusion
Overall this might be over kill, but it is a great way to add autocompletion, and verify that all the classes inside of your app are valid. Further it will only include the exact styling you need, so no need to analyze your code and remove classNames from Tailwind after the fact. It does that automatically for us.