Skip to Main Content
Let's Talk

Tailwind CSS is a core part of the Mostly Serious front end stack. Out of the box, it includes most of the utility classes you need to conditionally apply styles based on an element’s state.

Not only pseudo-classes, but aria states and more are available for you to write classes like the following:

<button class="hover:bg-blue-800 aria-expanded:bg-blue-700">I'm a button</button>

However, there are occasions when you will want to style an element with a variant that doesn't exist out of the box, such as a custom loading state. In those cases, it’s helpful to create your own custom state variants, and Tailwind’s plugin system makes it easy to do with just a few lines of code.

Creating a custom Tailwind CSS state variant

In this example, we’re going to create an is-active: custom variant that lets us apply styles conditionally for any elements that have an is-active class.

In its simplest form, it looks like this:

addVariant(`is-active`, [ `.is-active &`, `&.is-active` ])

This code can be added to your Tailwind config file as follows:

import plugin from 'tailwindcss/plugin' export default { // ... plugins: [ plugin(({ addVariant }) => { addVariant(`is-active`, [ `.is-active &`, `&.is-active` ]) }), ], }

We are using Tailwind’s addVariant helper function to apply styles if an element has an is-active class. In a template, you'd use it as follows.

<!-- variant applied --> <div class="is-active is-active:bg-pink-500"> <!-- variant not applied --> <div class="is-active:bg-pink-500">

That's all it takes to create a new variant; and, if you are using the Tailwind CSS IntelliSense extension in VS Code, your custom state variants will auto-complete for you as well.

Abstracting this into a variant generator function

Now, what if you needed to make several custom variants? Of course, you could repeat the addVariant code, but you could also create a function to generate it for you and keep our code DRY.

Have a look at the next example config file in which we define a function named "is" to create custom state variants.

/* tailwind.config.js */ import plugin from 'tailwindcss/plugin' const is = (name) => { let selector = `.is-${name}` return plugin(({ addVariant, _e }) => { addVariant(`is-${name}`, [ `${selector} &`, `&${selector}` ]) }) } // ...

This function receives a name argument and returns an addVariant plugin configured for our variant. Now we can use in later in the config file as follows:

/* tailwind.config.js */ import plugin from 'tailwindcss/plugin' const is = (name) => { let selector = `.is-${name}` return plugin(({ addVariant, _e }) => { addVariant(`is-${name}`, [ `${selector} &`, `&${selector}` ]) }) } export default { // ... plugins: [ is('active'), is('loading'), // ... add as many other states as you need ], }

You can then use this function to create any is-* variants you need.

Creating custom utilities for global application state

Just as we have done for single elements, we can use this same pattern to create custom state utilities that target the document element or body to conditionally style child elements based on available features or the global application state.

For example:

  • Style elements whether the method of input is keyboard or mouse. (You could use focus-within for this, too)
  • Show or hide elements based on whether animations are enabled globally
  • Test if the browser has access to Navigator.share
  • Or indicate that the site’s global flyout menu is open

Theses scenarios and more are a great use for custom variants.

/* these can be added to plugins[] in tailwind.config.js */ addVariant('animation-enabled', [ 'body[data-animation-enabled] &' ]) addVariant('web-share-enabled', [ 'body[data-web-share-enabled] &' ]) addVariant('personalization-enabled', [ 'body[data-personalization-enabled] &' ]) addVariant('scroll-locked', [ '&[data-scroll-locked]' ]) addVariant('flyout-active', [ '&[data-flyout-active]' ])

Will this work with future versions of Tailwind?

At the time of writing, plugins configured in javascript are on the roadmap to be supported in Tailwind 4 which is currently in development.

And recently there was a post on X showing a non-javascript way to configure variants in CSS.

Give custom variants a try

To sum up, Tailwind CSS plugins can be configured in just a few lines of javascript, and they make it easy to apply conditional styling for custom state. If there is not a built in utility class that meets your need, reach for addVariant and away you go.

We love building well-crafted websites that make users smile

Have you been struggling to keep your interface up to par with your user's expectations? Or maybe you love Tailwind CSS as much as we do and just want to say, "Hi!" Either way, we'd be happy to hear from you.