Twin is a library that empowers developers to build better apps by blending the powers of Tailwind CSS along with CSS-in-JS. This article examines Twin to understand how it works and provides a tutorial for using it in a React app.
What is Tailwind CSS?
If you’re not familiar with it already, Tailwind is a utility-based CSS library used for creating custom designs. Tailwind inherits most of its characteristics from Atomic CSS, an approach to element styling that offers single-purpose class names.
Tailwind differs from its alternatives like Bootstrap and Bulma in that it provides only the raw essentials needed for styling pages and components, as opposed to a default theme with predefined components.
For example, to style a card with Bulma’s default theme, simply add class = 'card'
to an element like a div
. Tailwind, on the other hand, requires you to be more specific in defining styles. Tailwind provides classes such as bg-white
to give an element a white background color, or px-4
for a padding of four on the x-axis.
As a result, a card designed using Tailwind’s utility classes will be similar to the following:
<div class="max-w-xs rounded overflow-hidden shadow-lg my-2"> <div class="px-6 py-4"> <div class="font-bold text-xl mb-2">The Coldest Sunset</div> <p class="text-grey-darker text-base"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil. </p> </div> </div>
What is CSS-in-JS?
CSS-in-JS is a pattern for writing component-scoped CSS using JavaScript. It’s important to note that CSS-in-JS is not a framework, but rather an authoring tool that allows you to implement different libraries.
How Twin works
Twin allows you to style frontend components using Tailwind classes, then compiles them into any supported CSS-in-JS library. When Babel runs over your JavaScript or Typescript files, Twin converts the classes into CSS objects before passing them to your chosen CSS-in-JS library, thereby erasing the need for an extra client bundle.
My favorite styling option is using the tw
prop as an alternative to the default className
prop that ships with React for passing Tailwind styles to an element.
Instead of styling a button with a blue background like this:
const Button = () => <button className ="bg-blue-200"> Click Me </button>
You could write it this way:
const Button = () => <button tw="bg-blue-200"> Click Me </button>
Twin also enables you write conditional styles in a more expressive way by giving you the option of nesting the tw
import within a CSS prop that accepts logic:
const ConditionalInput = ({ hasError }) => ( <input css={[tw`border p-4`, hasError && tw`border-red-700`]} /> ); const [errorPresent, setErrorPresent] = useState(true); //in render <div tw="m-3 flex flex-col"> <ConditionalInput hasError={errorPresent} /> <button tw="bg-green-500 mt-3 p-2 text-white " onClick={() => setErrorPresent(!errorPresent)} > {" "} Toggle Error Present </button> </div>
Using styled-components in Twin
In addition to styling elements using the tw
prop Twin provides, you can create elements by adding the tw
import at the top of your file, just as you would with styled-components:
import tw from "twin.macro"; const PrimaryButton = tw.button`bg-blue-800 text-white px-6 py-2 m-6 rounded-md hover:bg-blue-600`; function App() { return ( <PrimaryButton> Hello </PrimaryButton> ); } export default App;
Duplicating existing components
As a developer, you are bound to be in situations where you want to reuse existing components but add a little extra styling. Twin takes this into consideration by allowing you to clone existing components as well as specify additional styling to be applied to the new components:
import tw from "twin.macro"; const PrimaryButton = tw.button`bg-blue-800 text-white px-6 py-2 m-6 rounded-md hover:bg-blue-600`; const OtherButton = tw(PrimaryButton)`bg-purple-600 hover:bg-purple-200`; function App() { return ( <PrimaryButton> Hello </PrimaryButton> <OtherButton> Other Button</OtherButton> ); } export default App;
Benefits of using Twin
- Customizable CSS
- No extra build size
- Support for React and React frameworks
- Out-of-the-box support for Vue (currently in trial phase)
- CSS-in-JS integrations help solve complexity of adding custom animations in Tailwind
- Human-readable alerts for mistypings. For example, if you accidentally apply a nonexistent class like
bg-pink
without adding a specific shade to an element, Twin gives you an alert similar to this:

Using Twin in a React application
Although you can harness the power of Twin with other frameworks such as Gatsby, Next.js, and, more recently, React Native, this example is focused on using Twin in a React application.
Tutorial prerequisites
To follow this tutorial, you should have a React development environment. We’ll use the following:
- Create React App to scaffold a fresh React app
- Yarn as our package manager (alternative: npm)
- Emotion as our CSS-in-JS library (alternative: styled-components, Goober, etc.)
Set up your React app
First, create a fresh installation of React by running:
npx create react app twindemo
Once your React boilerplate has been generated, move into the folder using the cd
command and install the twin.macro
package by running:
yarn add tailwindcss twin.macro
Next, install Emotion:
yarn add @emotion/core @emotion/styled @emotion/react
Once Emotion is installed, generate a tailwind.config.js
file by running:
npm tailwindcss init --full
Now add a babelMacros
key in your project’s package.json
file:
"babelMacros": { "twin": { "config": "tailwind.config.js" } }
Next, import the base Tailwind stylesheet in your app’s index.js
file with:
import "tailwindcss/dist/base.min.css";
Tip: Since React 17, it is recommended you add
/** @jsxImportSource @emotion/react **/
at the top of yourJSX
files to import and use thetw
prop that Twin provides.
Once you do this, your App.js
file should contain code in this format:
/** @jsxImportSource @emotion/react */ import tw from "twin.macro"; function App() { return ( ... ); } export default App;
Replication example
Let’s see Twin in action by replicating this section from TailwindCSS’s official site.
The original snippet looks like this:
<figure class="md:flex bg-gray-100 rounded-xl p-8 md:p-0"> <img class="w-32 h-32 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512"> <div class="pt-6 md:p-8 text-center md:text-left space-y-4"> <blockquote> <p class="text-lg font-semibold"> “Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.” </p> </blockquote> <figcaption class="font-medium"> <div class="text-cyan-600"> Sarah Dayan </div> <div class="text-gray-500"> Staff Engineer, Algolia </div> </figcaption> </div> </figure>
If we were to implement this using Twin, the snippet would look like this:
/** @jsxImportSource @emotion/react */ import tw from "twin.macro"; function DemoTestimonial() { return ( <div tw="md:flex m-12 bg-gray-100 rounded-xl p-8 md:p-0"> <img tw="w-32 h-32 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="https://tailwindcss.com/_next/static/media/sarah-dayan.a8ff3f1095a58085a82e3bb6aab12eb2.jpg" alt="" width="384" height="512" /> <div tw="pt-6 md:p-8 text-center md:text-left space-y-4"> <blockquote> <p tw="text-lg font-semibold"> “Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.” </p> </blockquote> <div tw="font-medium"> <div tw="text-blue-600">Sarah Dayan</div> <div tw="text-gray-500">Staff Engineer, Algolia</div> </div> </div> </div> ); } function App() { return ( <DemoTestimonial/> ); } export default App;
If we preview the result in the browser, you get a fully responsive replica of the original snippet, only now we’re using Twin in our app.

Extra tip: Using babel-plugin-twin
If you want Babel to import the tw
prop in your JavaScript files automatically, you can make use of the babel-plugin-twin
package. This package removes the need to add import "twin.macro"
to the top of your files every time you want to access the utility classes Twin provides through Tailwind.
To do this, open up your fresh React project and set up Twin using the steps shown above. Once your setup is complete, install the babel-plugin-twin
package using your favorite package management tool using either of the commands below:
npm i -D babel-plugin-twin # or yarn add babel-plugin-twin -D
Next, register the babel-plugin-twin
package as a plugin in your babel.config.js
file. It is important that you register babel-plugin-twin
before babel-plugin-macros
to avoid issues when running a build of your app. Your babel.config.js
file should look like this after you add the package to the plugins
array:
module.exports = { // ... plugins: [ "[babel-plugin-twin", {debug: true}], //if you want to get debugging feedback "babel-plugin-macros", // ... ], };
Summary
In this article, we dove into understanding styled-components and why they’re needed before taking a look at Twin and how to use it in a React application.
You can find the examples highlighted in this article on this GitHub repository. Don’t forget to check out the official Twin documentation on GitHub to learn more about this fantastic package.
Get setup with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Can you share resources for using twin with react-natibe
I’m not sure about the mention of “babel-plugin-macros” in this article. Is it required for “babel-plugin-twin” to work or is it mentioned only so that people that already use “babel-plugin-macros” can avoid issues with “babel-plugin-twin”?