Tailwind CSS vs twin.macro: The Ultimate Showdown for Next.js and Sitecore Projects

A practical guide to choosing between Tailwind CSS and twin.macro for your Next.js + Sitecore setup.

October 29, 2024

By Eric Dimech

Choosing the Right Styling Tool for Performance and Flexibility

When you’re working on a Next.js project with Sitecore and TypeScript, choosing the right approach to styling can make all the difference. You want something that keeps development fast, your code clean, and lets you tackle both static and dynamic components without pulling your hair out. In this blog, I’ll break down Tailwind CSS and twin.macro—two awesome tools for styling—and help you decide which one fits your project best.

Why Tailwind CSS and TypeScript Are a Perfect Pair

Tailwind CSS is all about making styling fast, flexible, and scalable. It lets you build UI elements quickly by applying pre-defined utility classes directly in your TSX files. When you're working with Next.js, Tailwind’s compatibility with server-side rendering (SSR) and static site generation (SSG) makes it even better because you get fast load times and a fully responsive design right out of the gate.

Now, throw TypeScript into the mix, and things get even better. TypeScript adds that extra layer of type safety to your components, making sure your props and state are well-defined. That means fewer errors and a smoother development process overall. This combo lets you fly through UI development with confidence while keeping everything clean and consistent.

In a Next.js + Sitecore project, Tailwind CSS really shines when it comes to building consistent, responsive, static layouts. It's a massive time-saver for things like headers, footers, buttons, and forms—without needing to mess around with custom CSS. For projects that rely on structured, static content, Tailwind delivers exactly what you need, without the fuss.

Example: Tailwind CSS Example in TypeScript

type HeaderProps = {
  title: string;
  links: { href: string; label: string }[];
};

const Header = ({ title, links }: HeaderProps) => (
  <header className="bg-gray-900 w-full h-20 p-6 flex flex-row gap-20">
    <h2 className="text-white heading-2">{title}</h2>
    <nav>
      <ul className="flex flex-row gap-6">
        {links.map((link, index) => (
          <li key={index}>
            <a className="text-white body-medium" href={link.href}>
              {link.label}
            </a>
          </li>
        ))}
      </ul>
    </nav>
  </header>
);

export default Header;

This approach works great for static content, but when you start needing to switch up styles dynamically based on props, state, or Sitecore personalization rules, you may run into challenges. This is where twin.macro can be your new best friend.

Unleashing Dynamic Styling with twin.macro and TypeScript

twin.macro takes everything you love about Tailwind CSS and pairs it with CSS-in-JS libraries like styled-components or emotion. The result? You get to apply Tailwind’s utility classes dynamically, based on props, state, or whatever other logic you’re using in your TypeScript components without losing any of the simplicity that makes Tailwind great.

Example: Dynamic Styling with twin.macro

import tw from 'twin.macro';

type CardProps = {
  title: string;
  description: string;
  isFeatured?: boolean;
};

const Card = ({ title, description, isFeatured }: CardProps) => (
  <div
    css={[
      tw`p-4 rounded-md bg-white shadow-md`, // Base styles
      isFeatured && tw`bg-yellow-100`, // Conditionally apply styles
    ]}
  >
    <h2>{title}</h2>
    <p>{description}</p>
  </div>
);

export default Card;

In this example, twin.macro allows you to apply Tailwind styles conditionally, like adding bg-yellow-100 when the isFeatured prop is true. This kind of flexibility is ideal for Sitecore projects, where you need to dynamically adjust styles based on real-time data or personalized user content.

Mastering Responsive Design with twin.macro

Another gem in twin.macro is its ability to nest responsive styles under breakpoints like md: and lg:. It helps cut down on repetition and keeps your code looking neat and organized.

Example: Nesting Breakpoints (md:, lg:)

import tw from 'twin.macro';

type ProfileCardProps = {
  name: string;
  bio: string;
};

const ProfileCard = ({ name, bio }: ProfileCardProps) => (
  <div
    css={[
      tw`p-4 bg-white shadow-md rounded-md`, // Base styles
      tw`md:(p-6 text-lg) lg:(p-8 text-xl bg-gray-100)`, // Grouped responsive styles
    ]}
  >
    <h4>{name}</h4>
    <p>{bio}</p>
  </div>
);

export default ProfileCard;

What’s happening here? Instead of repeating md:p-6 md:text-lg and lg:p-8 lg:text-xl all over the place, you can group those under md:(...) and lg:(...). It keeps your code tidy and easier to manage, especially when building responsive layouts.

Keeping Your Code Clean: Prettier with Tailwind CSS and twin.macro

Now, let’s talk about keeping your code nice and neat. The Prettier Tailwind plugin is your best friend when it comes to automatically sorting and organizing your Tailwind utility classes. Whether you’re using Tailwind CSS directly or styling with twin.macro, the plugin will make sure all your utility classes are ordered properly, every time you save.

Before and After Prettier Example:

// before

<div css={[tw`bg-gray-100 text-lg flex p-6 flex-col md:p-8 lg:shadow-lg`]}>
  {/* Content */}
</div>
// after

<div css={[tw`flex flex-col p-6 text-lg bg-gray-100 md:p-8 lg:shadow-lg`]}>
  {/* Content */}
</div>

It’s simple, but a game-changer for keeping your code clean. The plugin groups related utilities together (like flex and p-6), making everything easier to read and reducing those head-scratching moments when you’re trying to figure out why a class isn’t applying as expected.

Tailwind CSS vs twin.macro: Which Is Right for Your Project?

  1. Workflow
    • Tailwind CSS: Perfect for quick static layouts. TypeScript keeps things safe by ensuring your props and state are always in check.
    • twin.macro: If you’re dealing with more dynamic styling (like personalized components), twin.macro gives you all the flexibility you need.
  2. Dynamic and Responsive Styling
    • Tailwind CSS: Great for static components but can get repetitive when handling more complex layouts or dynamic changes.
    • twin.macro: Excels at dynamic styling and makes responsive design a breeze with nested breakpoints like md: and lg:.
  3. Code Maintainability
    • Tailwind CSS: Awesome for smaller projects, but once things get big, you may find yourself managing a ton of utility classes.
    • twin.macro: Offers a more manageable approach for larger projects with complex layouts, dynamic data, or personalized content from Sitecore.
  4. Code Cleanliness with Prettier
    • Tailwind CSS + twin.macro: With the Prettier Tailwind plugin, your classes are automatically sorted in a consistent order, keeping your code easy to read and maintain.

Final Thoughts on Tailwind CSS vs twin.macro

When you’re working on a Next.js + Sitecore project with TypeScript, it all comes down to how dynamic your project needs to be. If you’re building a mostly static site, Tailwind CSS is a no-brainer—it’s fast, clean, and easy to use.

On the other hand, if you’re dealing with more complex, dynamic layouts or personalized data (which is a big deal in Sitecore), twin.macro gives you the flexibility you need to keep things manageable and maintainable over the long term. Add in Prettier to keep your utility classes in check, and you’ve got a recipe for a beautifully styled, highly dynamic project.

Photo of Fishtank employee Eric Dimech

Eric Dimech

Front End Developer

Eric is a passionate web developer with three years of hands-on experience in crafting dynamic and responsive web applications. His expertise spans across JavaScript/TypeScript, React, GraphQL, and Tailwind/SASS, allowing him to create seamless and engaging user experiences. He thrives on bringing innovative ideas to life and continuously expanding his skills to stay at the forefront of web development.