Oluwaseun Raphael Afolayan Developer and digital growth hacker. Saving the world one paragraph at a time. Aspiring ethical hacker and a WordPress evangelist.

Intro to Twin: Combining the best of Tailwind and CSS-in-JS

5 min read 1567

Introduction To Twin

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; 

Styled Components Elements Tw Prop Import

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:
Human Readable Mistyping Alert
Class not found error in Twin.

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 your JSX files to import and use the tw 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.

Replication Snippet Tailwind Css 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.

Twin Fully Responsive Replica
Our result is a fully responsive replica

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:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ 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>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Oluwaseun Raphael Afolayan Developer and digital growth hacker. Saving the world one paragraph at a time. Aspiring ethical hacker and a WordPress evangelist.

2 Replies to “Intro to Twin: Combining the best of Tailwind and…”

  1. 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”?

Leave a Reply