Skip to content

twin.macro is my new styling favorite

I have already made my love for styled-components pretty clear on many different posts in this blog. This blog is about a new absolute favorite - twin.macro.

Before we talk about twin.macro, we have to talk about TailwindCSS.

Tailwind CSS

Tailwind is a atomic CSS framework that has taken the world by storm. Everywhere you look, there would be a company/website using it. But atomic CSS has existed way before Tailwind - Tachyons is an early example.

One of my first exposures to this writing style was from Adam Wathan (who is also the author of Tailwind), when he wrote this (now) iconic blog post. It's super long, but he very well talks about what atomic CSS is and what we can achieve through it.

every line of new CSS is still an opportunity for new complexity

When Adam said he is releasing a new framework called Tailwind which enforces atomic CSS, I immediately wanted to check it out. I found the experience rather underwhelming, The tokens from CSS was already difficult enough, now I have to learn these short form tokens for them? Uggh.. this is hard.

From the website for Tailwind:

<figure class="rounded-xl bg-gray-100 p-8 md:flex md:p-0">
  <img
    class="mx-auto h-32 w-32 rounded-full md:h-auto md:w-48 md:rounded-none"
    src="/sarah-dayan.jpg"
    alt=""
    width="384"
    height="512"
  />
  <div class="space-y-4 pt-6 text-center md:p-8 md:text-left">
    <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>

This is not how any of us think of colors or spacing and they are new tokens that you have to learn. In commercial projects, designers who were not aware of our use of Tailwind created different colors, different spacings which crowded the theme file. I could not copy already written CSS from codepen projects and have it work (This seems to be the problem Tailwind UI solves). And despite all that, Tailwind succeeded.

I think the reason for Tailwind's success is better explained when talking about their second founder - Steve Schoger. Their first project together was a book named Refactoring UI which is still one of the best books that a developer can get to teach themselves some design. The book is derived from tips that Steve used to provide on Twitter for developers. Both Steve and Adam have given enough thought about making a developer successful in design and received feedback on it. They have applied several of these principles into the framework of Tailwind itself and that's where the magic numbers and color suites come from.

Make your ideas look awesome, without relying on a designer. - Refactoring UI

  • We need to write custom CSS at least sometimes
  • Conditionally apply classes and create custom components
  • Extract critical CSS so that pages load faster
  • Instead of enabling and disabling features, I need to be able to use what I want.
  • I wish I didn't have to learn all these classes.

twin.macro

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

twin lets you write tailwind classes and converts them to css-in-js counterparts - currently supporting popular libraries like styled-components and goober. This means that instead of purging classes from HTML on build, twin can maintain a cache of classes that are being used and include them in the final bundle.

If you have something working with Tailwind before, using twin.macro you can change className to tw:

function Cover() {
  return (
    <div className="flex-none w-48 relative">
      <img
        src="/classic-utility-jacket.jpg"
        alt=""
        className="absolute inset-0 w-full h-full object-cover"
      />
    </div>
  );
}

On twin:

import "twin.macro";

function Cover() {
  return (
    <div tw="flex-none w-48 relative">
      <img
        src="/classic-utility-jacket.jpg"
        alt=""
        tw="absolute inset-0 w-full h-full object-cover"
      />
    </div>
  );
}
  • If you need to write custom CSS, it's as simple as writing custom CSS.
<div tw="flex-none w-48 relative">
  <img
    src="/classic-utility-jacket.jpg"
    alt=""
    tw="absolute inset-0 w-full object-cover"
    { /* Using `css` prop from `styled-components`, `emotion`. */ }
    css="max-height: calc(100vh - 48px)"
  />
</div>
  • Create components and apply CSS
import tw, { styled } from 'twin.macro';

const Button = styled.button(({ isPrimary }) => [
  // Group related classes
  tw`px-2 py-4 m-2`,
  tw`border border-gray-600	border-dashed rounded-sm`
  // Custom CSS
  `box-shadow: 2px 2px 5px 0px rgba(50, 50, 50, 0.75);`
  // conditional styling
  isPrimary && tw`bg-indigo-400`
  // variant grouping
  tw`hover:(text-black underline)`
]);
  • All variants and media stylings are available and transpiled on demand.

From twin.macro examples:

import 'twin.macro'

const interactionStyles = () => (
  <div tw="hover:(text-black underline) focus:(text-blue-500 underline)" />
)

const mediaStyles = () => <div tw="sm:(w-4 mt-3) lg:(w-8 mt-6)" />

const pseudoElementStyles = () => (
  <div tw="before:(content block w-10 h-10 bg-black)" />
)

const stackedVariants = () => <div tw="sm:hover:(bg-black text-white)" />

const variantsInGroups = () => <div tw="sm:(bg-black hover:bg-white)">
  • If you don't want to learn all the freaking classes, you can now sponsor Sid for his UI Devtools.

  • I had mentioned that twin.macro transpiles to styled-components or emotion, but you can also set it to transpile the output down to goober.

Goober is a 1kB alternative to CSS-in-JS libraries with the same functionalities. You aren't going to be using a whole lot of features because tailwind anyway, so overload the size limits.

More on Goober

On installing twin.macro and goober, add a setting in package.json to set it up to use goober:

npm i goober twin.macro
{
  "babelMacros": {
    "twin": {
      "preset": "goober"
    }
  }
}

There is a lot more convienience features that you get to add in for babel, here's few examples:

Happy Tailwinding