DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Build an AI Chatroom With ChatGPT and ZK by Asking It How!
  • Subtitles: The Good, the Bad, and the Resource-Heavy
  • Loader Animations Using Anime.js
  • Next.js Theming: CSS Variables for Adaptive Data Visualization

Trending

  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  • Transforming AI-Driven Data Analytics with DeepSeek: A New Era of Intelligent Insights
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • A Complete Guide to Modern AI Developer Tools
  1. DZone
  2. Coding
  3. Languages
  4. Utility-First CSS With Tailwind

Utility-First CSS With Tailwind

We go through how you can use Tailwind to build a feature-rich user interface and also touch on some of Tailwind's more advanced features.

By 
Chris Draycott-Wheatley user avatar
Chris Draycott-Wheatley
·
Feb. 26, 19 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
10.9K Views

Join the DZone community and get the full member experience.

Join For Free

Utility-first CSS is the notion of composing many small utilitarian classes together. With this, the aim is to allow you to create robust, scalable, and responsive user interfaces for the web.

Tailwind is a CSS framework that provides a suite of utility classes out of the box. It also allows you to compose and add your own classes where required.

Tailwind doesn’t prescribe any specific look or feel. You’re free to build to your desired design without having to undo anything the framework offers up front.

A customizable config file drives the look and feel of your UI and provides default rules that you can tweak to suit your needs.

In this article, we’ll go into more depth on how you can use Tailwind to build a feature-rich user interface. We’ll also touch upon some of the more advanced features Tailwind has to offer.

Setup

Although Tailwind is available to use directly from a CDN it’s recommended to install it from npm. This allows you to take advantage of the full customisability Tailwind provides.

First, install Tailwind as a development dependency;

npm install tailwindcss --save-dev

Once installed, you need to initialize Tailwind’s configuration setup:

./node_modules/.bin/tailwind init [custom filename]

This command generates a config file called tailwind.js (or the custom filename provided). This file contains all the predefined rules for colors, breakpoints, fonts, margins, and more. The idea is that you remove a lot of the boilerplate and leave only the config required to build your UI. The file contains comments with an overview of each rule.

For example, the default responsive breakpoints provided by Tailwind are:

screens: {
  'sm':  '576px',
  'md':  '768px',
  'lg':  '992px',
  'xl':  '1200px',
},

These are the minimum widths used to generate CSS media queries.

If desired you could adjust these to be ergonomic breakpoints and even add your own:

screens: {
  'wrist':  '576px',
  'palm':  '768px',
  'lap':  '992px',
  'desk':  '1200px',
  'wall': '2560px'
},

Tailwind uses PostCSS to generate CSS output from the directives and config provided. This means integrating a build step into your current development process is necessary. Integration is possible via various build tools such as Webpack, Gulp and more. Being built on PostCSS means you’re able to leverage the ecosystem of plugins to adapt to your build process.

The simplest solution to get up and running is to create a CSS file and use the @tailwind directive to include all Tailwind’s utilities.

/* provides a set of base styles */
@tailwind preflight;

/* injects in any component classes created via plugins, place custom component classes below this */
@tailwind components;

/* injects in all Tailwind's utility classes, place custom utility classes below this */
@tailwind utilities;

You can use the Tailwind's npm module to build the CSS:

./node_modules/.bin/tailwind build [input file path] -o [output file path]

You can then reference this output file path in a <link /> tag in your HTML;

<link href="/bundle.css" rel="stylesheet" />

Utility Classes

Tailwind provides utility classes for a wide array of needs. This allows you to build a responsive, bespoke UI without necessarily needing to write any new CSS.

For example, .text-lg, by default, is 1.5 rem of the base font size. .bg-dark-red will apply a dark red background based on the settings in your config file.

The values set in rules are configurable from your config file. This means if you want .text-lg to be twice the size of your base font you can customize it to be.

You can apply state variants such as hover or focus in the same way as responsive styles. .hover:bg-blue will turn the background blue when hovering over the desired element.

To combine both responsive and state variants at the same time make sure you prefix the responsive variant first. .xl:hover:bg-blue will mean the background is only blue on hover at the xl breakpoint.

Anatomy of a Tailwind class

Anatomy of a Tailwind class

Below is an example of a card component with dark and light variations adapted from an example in Tailwind’s documentation.

This markup uses no custom CSS, just utility classes from Tailwind;

<div class="flex flex-wrap">
  <div class="p-6 w-full lg:w-1/2 xl:w-1/2">
    <div class="overflow-hidden shadow-lg">
      <img
        class="w-full"
        src="/sunset-warm.jpg"
        alt="Sunset with grass in the foreground"
      />
      <div class="px-6 py-4">
        <div class="font-bold text-xl mb-2">A Warm Sunset</div>
        <p class="text-grey-darker text-base">
          A large drop of sun lingered on the horizon and then dripped over and
          was gone, and the sky was brilliant over the spot where it had gone,
          and a torn cloud, like a bloody rag, hung over the spot of its going.
          And dusk crept over the sky from the eastern horizon, and darkness
          crept over the land from the east.
        </p>
      </div>
      <div class="px-6 py-4">
        <span
          class="inline-block bg-grey-lighter rounded-full px-3 py-1 text-sm font-semibold text-grey-darker cursor-pointer hover:text-grey-lighter hover:bg-grey-darker mr-2"
          >#photography</span
        >
        <span
          class="inline-block bg-grey-lighter rounded-full px-3 py-1 text-sm font-semibold text-grey-darker cursor-pointer hover:text-grey-lighter hover:bg-grey-darker mr-2"
          >#sunset</span
        >
        <span
          class="inline-block bg-grey-lighter rounded-full px-3 py-1 text-sm font-semibold text-grey-darker cursor-pointer hover:text-grey-lighter hover:bg-grey-darker"
          >#summer</span
        >
      </div>
    </div>
  </div>

  <div class="p-6 w-full lg:w-1/2 xl:w-1/2">
    <div class="overflow-hidden shadow-lg bg-grey-darkest">
      <img
        class="w-full"
        src="/sunset-cold.jpg"
        alt="Sunset with grass in the foreground"
      />
      <div class="px-6 py-4">
        <div class="font-bold text-xl mb-2 text-grey-light">A Cold Sunset</div>
        <p class="text-grey text-base">
          A large drop of sun lingered on the horizon and then dripped over and
          was gone, and the sky was brilliant over the spot where it had gone,
          and a torn cloud, like a bloody rag, hung over the spot of its going.
          And dusk crept over the sky from the eastern horizon, and darkness
          crept over the land from the east.
        </p>
      </div>
      <div class="px-6 py-4">
        <span
          class="inline-block bg-grey-darker rounded-full px-3 py-1 text-sm font-semibold text-grey-lighter cursor-pointer hover:text-grey-darker hover:bg-grey-lighter mr-2"
          >#photography</span
        >
        <span
          class="inline-block bg-grey-darker rounded-full px-3 py-1 text-sm font-semibold text-grey-lighter cursor-pointer hover:text-grey-darker hover:bg-grey-lighter mr-2"
          >#sunset</span
        >
        <span
          class="inline-block bg-grey-darker rounded-full px-3 py-1 text-sm font-semibold text-grey-lighter cursor-pointer hover:text-grey-darker hover:bg-grey-lighter"
          >#winter</span
        >
      </div>
    </div>
  </div>
</div>

View example on CodeSandbox.

One of the most powerful features we’re using here is the ability to create responsive grids with just a few classes. Flexbox powers this functionality under the hood.

By applying an outer .flex class we’re able to add classes to inner elements which dictate their widths. For example, w-full lg:w-1/2 xl:w-1/2 will create a full-width element at all breakpoints other than lg and xl, where the width will be half.

With this, you can create complex layouts by composing utility classes. You’re also free to create a grid layout that works for you rather than being prescribed one.

Generally, the utility class name syntax may take a while to get used to due to its shorthand nature. The comments within the generated config file and detailed website documentation are helpful.

Component Classes

As you may have noticed in the example above, you can get to a point where you’re applying lots of utility classes to a single element. Use the same element in many places and you’re now having to update each one every time you need to make a tweak. This isn’t scalable.

Luckily, Tailwind has a solution to reduce this in the form of component classes.

Component classes allow you to extract many utility classes as well as custom CSS into new classes. This will provide you with a single source of truth for a specific piece of functionality.

Using Tailwind’s @apply directive, we can create component classes to abstract these rules.

Here’s the exact same design as the card example above built using component classes where deemed necessary:

<div class="flex flex-wrap">
  <div class="p-6 w-full lg:w-1/2 xl:w-1/2">
    <div class="card">
      <img
        class="w-full"
        src="/sunset-warm.jpg"
        alt="Sunset with grass in the foreground"
      />
      <div class="px-6 py-4">
        <div class="heading">A Warm Sunset</div>
        <p class="text-grey-darker text-base">
          A large drop of sun lingered on the horizon and then dripped over and
          was gone, and the sky was brilliant over the spot where it had gone,
          and a torn cloud, like a bloody rag, hung over the spot of its going.
          And dusk crept over the sky from the eastern horizon, and darkness
          crept over the land from the east.
        </p>
      </div>
      <div class="px-6 py-4">
        <span class="pill pill-light hover:pill-light mr-2">#photography</span>
        <span class="pill pill-light hover:pill-light mr-2">#sunset</span>
        <span class="pill pill-light hover:pill-light">#summer</span>
      </div>
    </div>
  </div>

  <div class="p-6 w-full lg:w-1/2 xl:w-1/2">
    <div class="card bg-grey-darkest">
      <img
        class="w-full"
        src="/sunset-cold.jpg"
        alt="Sunset with grass in the foreground"
      />
      <div class="px-6 py-4 ">
        <div class="heading text-grey-light">A Cold Sunset</div>
        <p class="text-grey text-base">
          A large drop of sun lingered on the horizon and then dripped over and
          was gone, and the sky was brilliant over the spot where it had gone,
          and a torn cloud, like a bloody rag, hung over the spot of its going.
          And dusk crept over the sky from the eastern horizon, and darkness
          crept over the land from the east.
        </p>
      </div>
      <div class="px-6 py-4">
        <span class="pill pill-dark hover:pill-dark mr-2">#photography</span>
        <span class="pill pill-dark hover:pill-dark mr-2">#sunset</span>
        <span class="pill pill-dark hover:pill-dark">#winter</span>
      </div>
    </div>
  </div>
</div>

And here’s the newly created classes:

.heading {
  @apply font-bold text-xl mb-2;
}

.card {
  @apply overflow-hidden shadow-lg;
}

.pill {
  @apply inline-block rounded-full px-3 py-1 text-sm font-semibold mr-2 cursor-pointer;
}

@variants hover {
  .pill-light {
    @apply text-grey-lighter bg-grey-darker;
  }
  .pill-dark {
    @apply text-grey-darker bg-grey-lighter;
  }
}

.pill-light {
  @apply bg-grey-lighter text-grey-darker;
}

.pill-dark {
  @apply bg-grey-darker text-grey-lighter;
}

This approach reduces the number of classes in the markup whilst delivering the exact same design. We’ve moved reused patterns to new classes allowing a single source of truth.

You’ll also see that we’re still composing utility classes alongside component classes. It’s not a choice between one or the other, meaning you can have the best of both worlds.

For example, there’s no need to create a .card-dark class as that’d end up being @apply bg-grey-darkest;. You can use the .bg-grey-darkest class alongside the .card class in the markup.

This is one of the core concepts that gives Tailwind its power. While utilitarian at first glance, Tailwind doesn’t force that pattern upon you.

Directives

Tailwind comes with a suite of predefined directives, we’ve come across three in this article already:

  • the @tailwind directive which allows you to import the core of Tailwind.
  • the @apply directive which allows you to create new classes by composing other classes.
  • and the @variants directive, which we’ll take a deeper look at now.

In the card example above, we’re using the @variant directive to merge our pill hover styles into a single class. This is a powerful way of creating stateful variants of your own utility or component classes. You can use the @variant directive to create focus, active, or group-hover specific classes.

Here’s how we used the span style="font-weight: 400;">@variants rule in the example above to create hover variants for our custom component classes;

@variants hover {
  .pill-light {
    @apply text-grey-lighter bg-grey-darker;
  }
  .pill-dark {
    @apply text-grey-darker bg-grey-lighter;
  }
}

The @responsive directive will create a suite of responsive classes for any new classes you wrap within it;

@responsive {
  .blur {
    filter: blur(30px);
  }
}

This will generate classes prefixed with your responsive breakpoint names. For example, .sm:blur will only apply a blur to images at the small breakpoint.

Finally, the @screen directive allows you to target the already defined breakpoints. This avoids rewriting media queries and having to keep them in sync.

@screen md {
  .blur {
    filter: blur(20px);
  }
}

@screen lg {
  .blur {
    filter: blur(10px);
  }
}

This example will provide stepped levels of blurriness depending on the breakpoint to any images with the blurclass applied to it.

Directives are another powerful way to extend custom classes. They can help provide all the features you get from Tailwind’s utility classes to your own custom classes.

CSS-in-JS Integration

Here at NearForm, we use a variety of solutions to build user interfaces. We tailor solutions to the specific needs of clients and their projects.

CSS-in-JS libraries such as Styled Components or Emotion as well as frameworks such as Material-UI are amongst our arsenal. Given this, it’s worth investigating how Tailwind integrates with these tools.

Here’s an example of usage with Styled Components and React:

import React from "react";
import styled from "styled-components";
import tw from "tailwind.macro";
import sunsetWarm from "./sunset-warm.jpg";

const Container = styled.div`
  ${tw`p-6 w-full lg:w-1/2 xl:w-1/2`}
`;

const Card = styled.div`
  ${tw`overflow-hidden shadow-lg`}
`;

const Image = styled.img`
  ${tw`w-full`}
`;

const InnerContainer = styled.div`
  ${tw`px-6 py-4`}
`;

const Text = styled.div`
  ${props => props.type === "heading" && tw`font-bold text-xl mb-2`}
  ${props =>
    (props.type === "paragraph" || !props.type) &&
    tw`text-grey-darker text-base`}
`;

const Pill = styled.span`
  ${tw`inline-block bg-grey-lighter rounded-full px-3 py-1 text-sm font-semibold text-grey-darker cursor-pointer hover:text-grey-lighter hover:bg-grey-darker`}

  &:not(:last-child) {
    ${tw`mr-2`}
  }
`;

export const App = () => (
  <Container>
    <Card>
      <Image src={sunsetWarm} alt="Sunset with grass in the foreground" />
      <InnerContainer>
        <Text type="heading">A Warm Sunset</Text>
        <Text type="paragraph" as="p">
          A large drop of sun lingered on the horizon and then dripped over and
          was gone, and the sky was brilliant over the spot where it had gone,
          and a torn cloud, like a bloody rag, hung over the spot of its going.
          And dusk crept over the sky from the eastern horizon, and darkness
          crept over the land from the east.
        </Text>
      </InnerContainer>
      <InnerContainer>
        <Pill>#photography</Pill>
        <Pill>#sunset</Pill>
        <Pill>#summer</Pill>
      </InnerContainer>
    </Card>
  </Container>
);

In the example above, we’re using the Babel Macro babel-plugin-tailwind-components. This plugin exposes the tw`` tagged template literal function to convert Tailwind classes into style objects.

This example works well and allows further abstraction via Styled Component’s visual primitives.

There are some caveats worth highlighting when trying to integrate Tailwind into a CSS-in-JS toolchain:

  • You’re still going to need to amend your toolchain to accommodate a build step for Tailwind.
  • You’ve now got two places you can write styles. How do you ensure consistency?
  • The component style of a library like React feels somewhat at odds with the component style of Tailwind. You’re now able to componentize either via Tailwind with the @apply directive or in React with a component. A decision is now required on where any abstractions live.

If CSS-in-JS is your jam then there are ways to integrate the two workflows. However, you may end up not gaining the full benefits of either solution when used together.

Production Builds

By default, Tailwind clocks in at around 36kb in size without any customization, once minified and gzipped.

Once you begin to configure Tailwind to suit your UI look and feel you’ll be able to remove a lot of the predefined configuration Tailwind provides.

For example, if you trim the color configuration down from the 73 colors provided by Tailwind to 25 colors you’ll reduce the bundle size down to 18kb. Similarly, if you reduce down the breakpoints from 5 to 3 you’ll shave 14kb off the bundle size.

Finally, by integrating a tool like PurgeCSS into your workflow you can remove any unused classes at build time. This means you’re only ever serving the classes your application uses.

Final Thoughts

Tailwind takes the benefits of CSS utility classes and extends them. By allowing composability and extensibility Tailwind provides an impressive developer experience. One where you can be productive straight away whilst not being tied to a predefined look and feel.

The lack of prescribed visual styles can be a huge boost to productivity. The speed with which you can compose a complex, responsive UI with its own look and feel is staggering.

CSS Directive (programming) Build (game engine)

Published at DZone with permission of Chris Draycott-Wheatley, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Build an AI Chatroom With ChatGPT and ZK by Asking It How!
  • Subtitles: The Good, the Bad, and the Resource-Heavy
  • Loader Animations Using Anime.js
  • Next.js Theming: CSS Variables for Adaptive Data Visualization

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!