Skip to content

Aquent | DEV6

Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages

Utility-first CSS: An Introduction to Tailwind

Written by: Leonard Lacson

What is utility-first CSS?

Utility-first CSS allows for a style of using CSS where each class serves a particular purpose. Each class is defined in a stylesheet (or a configuration file), where they are named appropriately to match the functionality that they provide (For example, bg-red would give you background-color: red, as expected). In utility-first frameworks, there are specific classes for commonly-used CSS styles such as colours, font sizes, margins, padding, etc.

If you’ve ever had to write a CSS class with a name such as this-is-a-very-specific-class-name-for-one-very-specific-style, which matches the class’s visual function, then you’ve already written your own utility class!

With classes named accordingly, all that’s left for you to do is add the classes that you need into the markup. This saves time when debugging, as all styling information about the web page is self-explanatory and exists entirely in your template.

Since all classes are already defined in a configuration file, there is a shift of complexity from the stylesheets to the markup. The classes’ immutability promotes better template structure during the development process, encouraging developers to think about how the web page should be built, instead of having to come up with “hacks” or countless style overrides halfway into building the page, all for the purpose of making the design look acceptable.

What makes utility-first CSS different from various CSS frameworks?

You might be wondering, what makes the utility-first approach different from using CSS frameworks such as Bootstrap, Bulma, or Foundation? The answer to that is simple – a utility-first framework is supposed to be low-level and non-opinionated; it is not a UI kit. In other words, there are no ready-made components right off the bat, and there is no need to spend countless hours overriding a framework’s built-in styles should there be a need to create a website that has its own customized look-and-feel.

There are several utility-first CSS frameworks out there, all with their own configurations, features, and naming-conventions. Today we are going to look into Tailwind, an increasingly popular utility-first framework for building UI!

What is Tailwind?

Tailwind is a utility-first CSS framework currently maintained by Adam Wathan, Jonathan Reinink, and David Hemphill designed for rapid user interface development.

I’ll admit – the first time I looked into Tailwind (and utility-first in general), I was overwhelmed by my markup being a lot busier than what I’m used to. However, I slowly started to appreciate how descriptive the markup was.

What does Tailwind have to offer?

Let’s take a look at some of the notable features that makes Tailwind such a unique CSS framework:

Responsiveness

Tailwind allows you to apply utility classes based on various breakpoints, which makes it easier for your UI to be fully responsive. Here are the default breakpoints supported:

@media (min-width: 640px) { /* ... */ }

/* Medium (md) */
@media (min-width: 768px) { /* ... */ }

/* Large (lg) */
@media (min-width: 1024px) { /* ... */ }

/* Extra Large (xl) */
@media (min-width: 1280px) { /* ... */ }

In order to apply a utility class to a specific breakpoint, you need to prefix it with the breakpoint name. You can use this prefixing for ALL utility classes.

Un-prefixed classes apply to all screen sizes, but prefixed classes only take effect at the specified breakpoint.

In this example, the height of 32 is by default, whereas it’s 48 on medium screens and 64 on large screens:

<div class="h-32 md:h-48 lg:h-64"></div>

Breakpoints can also be customized through the tailwind configuration file. Check out the documentation on how to define your own breakpoints.

Pseudo-classes

Similar to prefixes for responsiveness, there are also prefixes to handle pseudo-classes such as focus, hover, and more.

In the example below, we use the hover: prefix in order to add a class on hover:

<button class="bg-transparent hover:bg-red-500 text-red-700 hover:text white...">
    Hover over me!
</button>

By default, Tailwind does not enable pseudo-class variants for all utility classes. To see a complete list of variants that are enabled, go to the reference table linked here.

Custom variants can be written if Tailwind doesn’t support the pseudo-class that you’re looking for.

You can combine pseudo-class prefixes with responsive prefixes. This can be achieved by adding the responsive prefix first, and then the pseudo-class prefix.

In the example below, we have a red button with a shade of 500. The moment we hover over it, we change the colour to show UI feedback. Mind you, these two styles only apply on a small (sm) screen. Other utility classes can be added for other screen sizes (default, md, lg, and so on).

<button class="sm:bg-red-500 sm:hover:bg-red-600">Button</button>

Components

When using Tailwind, it is encouraged to build out your application entirely with utility classes so you can spot duplicated patterns, and then build out components afterwards.

Using this approach prevents early abstraction of your code, since that might lead to plenty of refactoring in the long run.

The main purpose of extracting components is to keep a lot of your code in sync. The moment you see utility class combinations repeated multiple times, it might be time to extract those into a component. This would keep your utility classes consistent across multiple pages and avoid the hassle of having to make the same changes in several places just to keep things consistent.

You can take a look at the Tailwind documentation’s examples of how to extract HTML components and how to extract CSS components using one of its built-in directives called @apply.

Directives

Utility classes aren’t the only features that you get access to when using Tailwind. This library also exposes custom directives and functions to use with your CSS.

There are five directives available in Tailwind:

  • @tailwind: we use this directive to add Tailwind’s base, components, utilities and screens styles into your CSS.
  • @apply: this directive inlines existing utility classes into your custom CSS.
  • @variants: this directive can be used to generate pseudo-class variants for your own utility classes.
  • @responsive: we use this directive to generate responsive variants for your own utility classes.
  • @screen: can be used to create media queries that reference breakpoints by name rather than their values which can be duplicated in your CSS.
  • To see each directive in action, you can check Tailwind’s documentation on directives.

In addition to the directives above, there is also a function available in Tailwind called theme(). This function allows you to access config values using dot notation. You can use theme() if you would like to reference a value from your configuration as part of your declaration.

.btn-red {
    background-color: theme('colors.red.600');
}

Customization and Composition

Unlike a lot of other CSS frameworks, Tailwind is made to be customized.

There is an optional tailwind.config.js file at the root of your project where you can define any customizations. Tailwind will look through this file, and since each section of the file is optional, the default configuration will be used for any missing sections.

You can create this tailwind.config.js file through:

npx tailwind init

The following are the most important sections of a tailwind configuration file:

  • Theme: this section allows you to define the main visual aspects of your application such as breakpoints, colours, and fonts.
  • Variants: this section allows you to specify which pseudo-class variants are generated for each core plugin such as borderColor, outline, zIndex, etc.
  • Plugins: this section allows you to use third-party plugins with Tailwind. You can also learn how to write your own plugins here.
  • Prefix: this section allows for custom prefixes to be added to all utility classes. Keep in mind that responsive or pseudo prefixes will always be appended in front of these prefixes.

Accessibility

When building your application with Tailwind, it’s important to note that it takes accessibility into account. There are utility classes made for improving accessibility with screen readers: sr-only and not-sr-only.

sr-only is used to visually hide elements without hiding them from the screen reader. See an example of it below:

<span class="sr-only">SR only!</span>

On the other hand, not-sr-only can be used to undo sr-only. This is useful for targeting specific screen sizes when hiding/showing elements to screen readers. In our example below, SR is applied to everything EXCEPT for large screens:

<span class="sr-only lg:not-sr-only">SR for non-lg screens!</span>

These accessibility utilities can also be used with pseudo-variants such as responsive and focus. A popular use-case for this is making an element visually hidden by default and showing it as soon as the user tabs into the element (e.g. “Skip to” elements).

Pros and Cons

Each framework comes with its strengths and weaknesses. However unique and powerful Tailwind may be, it still comes with some shortcomings that we’ll be discussing in this section. Let’s take a look on whether or not Tailwind is your cup of tea.

Pros

  • Easy to adopt: since all utility classes are named appropriately, it is easier for developers to jump into a project and use Tailwind with no prior experience. Also, if there are other projects that use Tailwind, it is easy to transition from one project to the other due to the consistency that utility classes bring to the table.
  • Built to be customized: even though it comes with a nice default configuration, Tailwind is built for customization straight out-of-the-box. This means that designers can work closely with developers on modifying the styles that exist in the configuration file. This allows teams to create their very own design systems. Gone are the days where you have to constantly override and fight your CSS framework!
  • Naming: using Tailwind’s utility classes almost eliminates the need for coming up with meaningful names for your CSS classes. Each utility class is as descriptive and low-level as can be; which means you can achieve the “look” that you expect, especially when using multiple utility classes together.
  • Less distractions: having all styling information live in one place means there is no need to switch back-and-forth between your CSS and markup when building and/or debugging your web page.
  • Smaller size (Let me explain this one!): although Tailwind’s CSS unminified size comes at around 780kb, there are various steps that can be taken to mitigate this. Using compression such as Brotli or gzip brings this down to ~23kb or 78kb, respectively. Purgecss can also be setup to remove CSS that you’re not using in your project; potentially bringing your compressed CSS bundle size down to under 10kb when used with Brotli/gzip.
  • Less likely to break other pages: since you do most of your work directly on the template with pre-defined classes, it is highly unlikely that you could break another page’s style. This is not the case in traditional CSS – introducing a CSS change on one page might affect another. This eliminates side effects when working on your template.

Cons

  • Markup is crowded: this is something you notice right away when using utility-first libraries. Since ALL of the styling lives in your markup, it tends to look messy if you’re not used to it. However, as you design your website/application, you start to appreciate the descriptiveness of each template.
  • Default bundle size is large: as mentioned earlier, Tailwind is massive before compression and Purgecss is introduced. It’s also annoying to set-up Purgecss if you’re not very familiar with it. Thankfully, Tailwind provides some documentation to ease this process.
  • Lots of repetition: as you build and style your application, you might feel exhausted by the amount of times you have to write the same utility. However, this is the time you should think about composition; the beauty of using the utility-first approach is building out and extracting components the moment you see a lot of repetitive patterns. 

Conclusion

Tailwind helps developers rapidly design and build user interfaces with its utility-first approach. This article does not promote utility-first or using Tailwind as “better” than using BEM, Bootstrap or Foundation. Each approach has its own pros and cons. Like many things in the world of software development, there is no right or wrong in choosing your CSS approach – it’s all a matter of preference, what fits your team and your project. However, I do believe that using utility-first/Tailwind offers a fresh perspective on how to build and style user interfaces for the web.

Websites that use Tailwind

I’ve found some great-looking pages that use Tailwind as their CSS framework. Here’s a short list of some pages that I’ve found:

You can also check out a huge list of websites built with Tailwind at https://builtwithtailwind.com/

References

Some useful references and resources if you’d like to learn more about Tailwind: