Twin.macro Tutorial for Beginners: Styling with Tailwind CSS and Emotion in React

Adeola Adeyemo J.
5 min readFeb 8, 2021

How to set up your React project to use Tailwind CSS and Emotion using Twin.macro

Photo by Sara Cervera on Unsplash

Introduction

First a little overview of each library based on their documentation:

Emotion is a library designed for writing css styles with JavaScript. It provides powerful and predictable style composition in addition to a great developer experience with features such as source maps, labels, and testing utilities. Both string and object styles are supported.

Tailwind is a utility-first CSS framework packed with classes like that can be composed to build any design, directly in your markup.

Twin blends the magic of Tailwind with the flexibility of css-in-js

I never felt I could need a styling solution that involved merging two libraries. However, as a good saying goes:

I can’t say I wasn’t hoping for it, but I didn’t see it coming.”
Jessi Kirby, Golden

Tailwind gives the advantage of a utility first CSS approach, which can be tailored to build unique interfaces. Tailwind also ensures that your stylesheet does not grow exponentially with your project size. Also, Tailwind’s classes pose less cognitive load with intuitive class names (you get used to it after a short while).

Emotion helps to solve the cascading problem since styles are scoped. It also generates class names out of the box which can have the component names as an attached label to aid readability. It helps to manage specificity with our selectors.

Each can stand alone and be sufficient. However, I was faced with a unique problem that involved letting the end-users overwrite styles (add Custom CSS). I also needed to use JavaScript variables to generate classes conditionally, but avoid the use of inline styles which can be a headache to overwrite.

After some research, I stumbled on Twin.macro which solves this unique problem by merging the usability of CSS-in-JS libraries like Emotion with the Utility approach given by Tailwind. Tailwind’s classes are composed of style declarations. This is useful for teams that are already familiar with Tailwind.

One of Tailwind’s weaknesses is creating complex animations. Working with Twin.macro also helps to attach custom animation to classes through the CSS-in-JS declaration.

In this tutorial, I will walk you through the steps to set up a React project to use Twin.macro. Then, we will look at a few examples of how to use this library for different use cases.

To follow along, you need a basic understanding of how JSX works.

Installation

  1. First, install Emotion as a dependency
yarn add @emotion/styled @emotion/react

Emotion requires you to do the following to use the css prop. Without this, your classes appear like [object object].

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

To avoid importing jsx in all files, other methods can be found in the official documentation. I use the @emotion/babel-preset-css-prop preset mostly.

2. Install tailwind CSS as a dependency or dev dependency (this depends on whether you want to extend some functionalities).

yarn add tailwindcss

Generate tailwind.config.js to extend as you see fit. We’ll discuss this in a bit more detail later.

npx tailwindcss init --full

3. Next, install Twin.macro as a dependency:

yarn add twin.macro

Then install a plugin for babel macros:

yarn add --dev babel-plugin-macros @babel/core

Add a new babelMacros key to yourpackage.json to configure Twin.

"babelMacros": {
"twin": {
"preset": "emotion"
}
},

This selects Emotion as the CSS-in-JS library to use with Twin.macro.

Create a .babelrc file at the root of your project. Add the babel-plugin-macros to your babel configuration file.

{
"plugins": [
"babel-plugin-macros",
],
}

We are all done. Next, we will make use of twin.macro to create and style components.

Note, to set up VSCode to give auto-complete options for twin.macro, check this out

Usage Examples

I picked and modified some examples from the Github documentation.

  1. Using the tw prop directly with jsx elements:
import 'twin.macro';const One = () => <div tw="text-blue-300">One</div>;

2. Nesting the tw prop to add conditional styles:

import tw from 'twin.macro';const Two = ({ hasHover }) => (
<div css={[tw`border border-solid border-black`, hasHover && tw`hover:border-blue-300`]}>Two</div>
);

3. Using Emotion’s css prop to add conditional styles:

import tw, { css } from 'twin.macro';// Notice that Emotion's css prop is imported directly from twin.macroconst hoverStyles = css`
&:hover {
border-color: black;
${tw`text-black border-solid`}
}
`;
const Three = ({ hasHover, text = "Three" }) => (
<div css={[tw`border`, hasHover && hoverStyles]}>{text}</div>
);

4. Using tw prop to create styled components:

import tw from 'twin.macro';const Four = tw.div`border border-solid border-blue-300 hover:border-black`;

5. Clone existing styled components with extra styling:

import tw from 'twin.macro';const Four = tw.div`border border-solid border-blue-300 hover:border-black`;const Five = tw(Four)`border-purple-500`;

6. Using Emotion’s styled import for conditional styling:

import tw, { styled } from 'twin.macro';const StyledSix = styled.div(({ hasBorder }) => [
`color: black;`,
hasBorder && tw`border-purple-500 border-solid border rounded-sm`
]);
const Six = () => <StyledSix hasBorder>Six</StyledSix>;

7. Using the backticks to add custom styling to styled components:

import tw, { styled } from 'twin.macro';const StyledSeven = styled.div`
color: black;
${({ hasBorder }) => hasBorder && tw`border border-solid border-purple-500`}
`;
const Seven = () => <StyledSeven hasBorder>Seven</StyledSeven>;

8. Merging multiple styling formats in styled components:

import tw, { styled } from 'twin.macro';const StyledEight = styled.div`
${tw`text-sm`};
${({ hasBorder }) => hasBorder && tw`border-purple-500`};
${({ color }) => color && `
color: ${color};
`};
font-weight: 500;
`;
const Eight = () => (
<StyledEight
hasBorder
color="blue"
>
Eight
</StyledEight>
);

9. Nesting the tw prop in a styled component:

import tw, { styled } from 'twin.macro';export const StyledNine = styled.div`
span {
${tw`text-blue-800`};
}
`;
export const Nine = () => (
<StyledNine>
<span>Nine</span>
</StyledNine>
);

All examples are also in this Sandbox.

More usage patterns and features are up to your imagination and knowledge of JavaScript, Emotion, and Twin.macro's features.

Next, we will extend our tailwind configuration to use custom classes with Twin.macro.

Tailwind Config Extension

Twin.macro works directly with tailwind.config.js .

For example, if you want to add a specific border-radius of 1px (which is not available by default), you can directly edit the tailwind.config.js the following way:

module.exports = {
theme: {
extend: {
borderRadius: {
xs: "1px",
}
}
}
};

The new border-radius property becomes available in the form of rounded-xs which we will add as a class.

import tw, { styled } from "twin.macro";const Wrapper = styled.div`
${tw`rounded-xs p-4`};
font-size: 26px;
font-family: sans-serif;
> * {
${tw`my-3 p-3`};
}
`;

This shows the power of theming that Tailwind provides by default. To read more about extending the tailwind configuration, check out the official documentation.

Support

Twin.macro is also available for JavaScript frameworks like Gatsby, and Next.js. Vue.js is only an experimental version at the moment. Twin.macro can also be used with Styled Components.

This the end of the tutorial; I hope you found this useful to get started with Twin.macro. Once again, to view all examples in this tutorial, check out this Sandbox.

You can share your feedback with me on Twitter, in a tweet, or below.

--

--