How to Extend Tailwind CSS

Tailwind CSS packs a punch as a utility-first CSS framework. Even with some amazing defaults at your disposal, there comes a time where you might need to extend the framework. In this post, learn how to extend Tailwind CSS even further.

Use Cases

Each use case below is what I would do. In no way are they meant for how you should approach it yourself. Consider these tips and tricks I have learned by working with the framework over time.

Internal/Personal approach

The first and probably obvious way to extend Tailwind is to use a CSS approach. Doing this might look like the following:

Note: In this video/example I reference the playground project on the Tailwind CSS Github project page. This uses basic dependencies to help you jump right in. In previous installments of this series I walk you through how to get it up and running. Find those linked at the end of this post.

Adding new utilities via CSS

New utility classes should come only after the @tailwind directives in your main stylesheet.

/* tailwind.css*/
@tailwind base;
@tailwind components;
@tailwind utilities;

.rotate-0 {
  transform: rotate(0deg);
}
.rotate-90 {
  transform: rotate(90deg);
}
.rotate-180 {
  transform: rotate(180deg);
}
.rotate-270 {
  transform: rotate(270deg);
}

Those classes now work alongside everything bundled with your Tailwind configuration. I should note that I am, using the default supplied by the Tailwind CSS docs.

What if we wanted the rotate- classes to also be responsive? Enter the handy supplied @responsive directive.

/* tailwind.css*/
...
@responsive { 
  .rotate-0 {
    transform: rotate(0deg);
  }
  .rotate-90 {
    transform: rotate(90deg);
  }
  .rotate-180 {
    transform: rotate(180deg);
  }
  .rotate-270 {
    transform: rotate(270deg);
  }
}

What about hover/focus states? The same concept as @responsive applies here. We can even wrap everything within @responsive like the following:

/* tailwind.css*/
@responsive {
  @variants hover, focus {
    .rotate-0 {
      transform: rotate(0deg);
    }
    .rotate-90 {
      transform: rotate(90deg);
    }
    .rotate-180 {
      transform: rotate(180deg);
    }
    .rotate-270 {
      transform: rotate(270deg);
    }
  }
}

Pretty cool! This use case probably makes the most since for internal projects. Those might be projects created on your own or in a team setting. If you were to use Sass or Less you could extract these extra utilities into a partial and import the CSS using @import to cut down on the amount of lines of code in your files.

Fancy PostCSS approach

With the @apply statement adding existing Tailwind utility classes is completely possible. Maybe you want to define a few components for forms and make use of Tailwind. Ideally you want to extract all the classes from your HTML markup. That might look something like this.

.input {
  @apply appearance-none block w-full bg-white text-gray-700 border border-black rounded py-3 px-4 leading-none; 
}

.select {
  @apply appearance-none py-3 px-4 pr-8 block w-full bg-white border border-black text-gray-700
   rounded leading-tight;
  -webkit-appearance: none;
}

The benefit here is to group already existing Tailwind utility classes for reuse inside a single scoped class name. Elements in your app/website that are often repeated are the best candidate for this type of grouping.

External / Open Source approach

Another approach towards extending Tailwind CSS involves more JavaScript configuration. While a little more complex, this approach allows you to extract the logic for others(or you) to use elsewhere. You could wrap it up as a node module for example, or even just a library of modules that define custom components for you or others. The sky is the limit.

Adding button components for example, (without writing any additional CSS in the stylesheet of your project) might look like the following:

// tailwind.config.js
module.exports = {
  // a bunch of other tailwind settings
  plugins: [
    function({ addComponents }) {
      const buttons = {
        '.btn': {
          padding: '.5rem 1rem',
          borderRadius: '.25rem',
          fontWeight: '600',
        },
        '.btn-primary': {
          backgroundColor: '#4299e1',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#3182ce'
          },
        },
        '.btn-secondary': {
          backgroundColor: '#667eea',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#5a67d8'
          },
        },
      }

      addComponents(buttons)
    }
  ]
}

Notice the addComponents function is specific to the Tailwind config. We destructure it and are able to pass an object of CSS-in-JS. We define this function within the plugins array inside the tailwind.config.js file accordingly. You could also require this from another location in your project or even from a separate node module.

// tailwind.config.js
module.exports = {
  plugins: [
    require('./plugins/buttons')
  ]
}

The main disadvantage I see here is not having access to pre-existing utility classes for use in the direct CSS. You could reference a given users configuration with a little more configuration, but that’s a bit more complex to do so.

On top of the addComponents function there are several others you can hook into and extend Tailwind with. Here is a full list and a quick summary of each:

  • addUtilities(), for registering new utility styles
  • addComponents(), for registering new component styles
  • addBase(), for registering new base styles
  • addVariant(), for registering custom variants
  • e(), for escaping strings meant to be used in class names
  • prefix(), for manually applying the user’s configured prefix to parts of a selector
  • theme(), for looking up values in the user’s theme configuration
  • variants(), for looking up values in the user’s variants configuration
  • config(), for looking up values in the user’s Tailwind configuration

I often find myself extending the @tailwind base using addBase() as a plugin. I set heading levels to specific defaults accordingly since they are completely reset in size and style otherwise.

// tailwind.config.js
module.exports = {
  plugins: [
    function({ addBase, config }) {
      addBase({
        'h1': { fontSize: config('theme.fontSize.3xl') },
        'h2': { fontSize: config('theme.fontSize.2xl') },
        'h3': { fontSize: config('theme.fontSize.xl') },
        'h4': { fontSize: config('theme.fontSize.lg') },
      })
  ]
}

I recommend giving the plugin documentation a good read. There’s so much more I haven’t covered here you can do in your own projects to configure and extend Tailwind CSS quite easily.

Much of the code examples and documentation snippets above are taken from tailwindcss.com which is built and maintained by Adam Wathan. Go support him!

The Series So Far