Tailwind CSS on GitHub

Hover, Focus, & Other States

Using utilities to style elements on hover, focus, and more.

Overview

Similar to how Tailwind handles responsive design, styling elements on hover, focus, and more can be accomplished by prefixing utilities with the appropriate state variant.

<form>
  <input class="border border-transparent focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent ...">
  <button class="bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-opacity-50 ...">
    Sign up
  </button>
</form>

Not all state variants are enabled for all utilities by default due to file-size considerations, but we’ve tried our best to enable the most commonly used combinations out of the box.

For a complete list of which variants are enabled by default, see the reference table at the end of this page.

If you need to target a state that Tailwind doesn’t support, you can extend the supported variants by writing a variant plugin.


Hover

Add the hover: prefix to only apply a utility on hover.

<button class="bg-red-500 hover:bg-red-700 ...">
  Hover me
</button>

By default, the hover variant is enabled for the following core plugins:

  • backgroundColor
  • backgroundOpacity
  • borderColor
  • borderOpacity
  • boxShadow
  • gradientColorStops
  • opacity
  • rotate
  • scale
  • skew
  • textColor
  • textDecoration
  • textOpacity
  • translate

You can control whether hover variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      padding: ['hover'],
    }
  },
}

Focus

Add the focus: prefix to only apply a utility on focus.

<input class="focus:ring-2 focus:ring-blue-600 ...">

By default, the focus variant is enabled for the following core plugins:

  • accessibility
  • backgroundColor
  • backgroundOpacity
  • borderColor
  • borderOpacity
  • boxShadow
  • gradientColorStops
  • opacity
  • outline
  • placeholderColor
  • placeholderOpacity
  • ringColor
  • ringOffsetColor
  • ringOffsetWidth
  • ringOpacity
  • ringWidth
  • rotate
  • scale
  • skew
  • textColor
  • textDecoration
  • textOpacity
  • translate
  • zIndex

You can control whether focus variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      maxHeight: ['focus'],
    }
  },
}

Active

Add the active: prefix to only apply a utility when an element is active.

<button class="bg-green-500 active:bg-green-700 ...">
  Click me
</button>

By default, the active variant is not enabled for any core plugins.

You can control whether active variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      backgroundColor: ['active'],
    }
  },
}

Group-hover

If you need to style a child element when hovering over a specific parent element, add the group class to the parent element and add the group-hover: prefix to the utility on the child element.

New Project

Create a new project from a variety of starting templates.

<div class="group border-indigo-500 hover:bg-white hover:shadow-lg hover:border-transparent ...">
  <p class="text-indigo-600 group-hover:text-gray-900 ...">New Project</p>
  <p class="text-indigo-500 group-hover:text-gray-500 ...">Create a new project from a variety of starting templates.</p>
</div>

By default, the group-hover variant is enabled for the following core plugins:

  • backgroundColor
  • backgroundOpacity
  • borderColor
  • borderOpacity
  • boxShadow
  • opacity
  • textColor
  • textDecoration
  • textOpacity

You can control whether group-hover variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      divideColor: ['group-hover'],
    }
  },
}

Group-focus

The group-focus variant works just like group-hover except for focus:

<button class="group bg-yellow-500 focus:bg-yellow-600 ...">
  <svg class="text-white group-focus:text-yellow-300 ..."></svg>
  Bookmark
</button>

By default, the group-focus variant is not enabled for any core plugins.

You can control whether group-focus variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      backgroundColor: ['group-focus'],
    }
  },
}

Focus-within

Add the focus-within: prefix to only apply a utility when that element or any of its descendants have focus.

<form>
  <div class="text-gray-400 focus-within:text-gray-600 ...">
    <div class="...">
      <svg fill="currentColor"></svg>
    </div>
    <input class="focus:ring-2 focus:ring-gray-300 ...">
  </div>
</form>

By default, the focus-within variant is enabled for the following core plugins:

  • accessibility
  • backgroundColor
  • backgroundOpacity
  • borderColor
  • borderOpacity
  • boxShadow
  • opacity
  • outline
  • ringColor
  • ringOffsetColor
  • ringOffsetWidth
  • ringOpacity
  • ringWidth
  • textColor
  • textDecoration
  • textOpacity
  • zIndex

You can control whether focus-within variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      scale: ['focus-within'],
    }
  },
}

Focus-visible

Note that focus-visible currently requires JS and PostCSS polyfills for sufficient browser support.

Add the focus-visible: prefix to only apply a utility when an element has focus but only if the user is using the keyboard.

<button class="focus:ring-2 focus:ring-red-500 ...">
  Ring on focus
</button>
<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 ...">
  Ring on focus-visible
</button>

Note that currently only Chrome, Edge, and Firefox support focus-visible natively, so for sufficient browser support you should install and configure both the focus-visible JS polyfill and the focus-visible PostCSS polyfill. Make sure to include the PostCSS plugin after Tailwind in your list of PostCSS plugins:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    'postcss-focus-visible': {},
    autoprefixer: {}
  }
}

By default, the focus-visible variant is not enabled for any core plugins.

You can control whether focus-visible variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      textDecoration: ['focus-visible'],
    }
  },
}

Motion-safe

Add the motion-safe: prefix to only apply a utility when the prefers-reduced-motion media feature matches no-preference.

For example, this button will only animate on hover if the user hasn’t enabled “Reduce motion” on their system.

<button class="transform motion-safe:hover:scale-110 ...">
  Hover me
</button>

Note that unlike most other variants, motion-safe can be combined with both responsive variants and other variants like hover, by stacking them in this order:

<div class="sm:motion-safe:hover:animate-spin">
  <!-- ... -->
</div>

By default, the motion-safe variant is not enabled for any core plugins.

You can control whether motion-safe variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      animation: ['motion-safe'],
    }
  },
}

Motion-reduce

Add the motion-reduce: prefix to only apply a utility when the prefers-reduced-motion media feature matches reduce.

For example, this button will animate on hover by default, but the animations will be disabled if the user has enabled “Reduce motion” on their system.

<button class="transform hover:scale-110 motion-reduce:transform-none ...">
  Hover me
</button>

Note that unlike most other variants, motion-reduce can be combined with both responsive variants and other variants like hover, by stacking them in this order:

<div class="sm:motion-reduce:hover:animate-none">
  <!-- ... -->
</div>

By default, the motion-reduce variant is not enabled for any core plugins.

You can control whether motion-reduce variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      animation: ['motion-reduce'],
    }
  },
}

Disabled

Add the disabled: prefix to only apply a utility when an element is disabled.

<button class="disabled:opacity-50 ...">
  Submit
</button>
<button class="disabled:opacity-50 ..." disabled>
  Submit
</button>

By default, the disabled variant is not enabled for any core plugins.

You can control whether disabled variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      opacity: ['disabled'],
    }
  },
}

Visited

Add the visited: prefix to only apply a utility when a link has been visited.

<a href="#" class="text-blue-600 visited:text-purple-600 ...">Link</a>

By default, the visited variant is not enabled for any core plugins.

You can control whether visited variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      textColor: ['visited'],
    }
  },
}

Checked

Add the checked: prefix to only apply a utility when a radio or checkbox is currently checked.

<input type="checkbox" class="appearance-none checked:bg-blue-600 checked:border-transparent ...">

By default, the checked variant is not enabled for any core plugins.

You can control whether checked variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      backgroundColor: ['checked'],
      borderColor: ['checked'],
    }
  },
}

First-child

Add the first: prefix to only apply a utility when it is the first-child of its parent. This is mostly useful when elements are being generated in some kind of loop.

<div class="...">
  <div v-for="item in items" class="transform first:rotate-45 ...">
    {{ item }}
  </div>
</div>

It’s important to note that you should add any first: utilities to the child element, not the parent element.

By default, the first-child variant is not enabled for any core plugins.

You can control whether first variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      borderWidth: ['first'],
    }
  },
}

Last-child

Add the last: prefix to only apply a utility when it is the last-child of its parent. This is mostly useful when elements are being generated in some kind of loop.

<div class="...">
  <div v-for="item in items" class="transform last:rotate-45 ...">
    {{ item }}
  </div>
</div>

It’s important to note that you should add any last: utilities to the child element, not the parent element.

By default, the last-child variant is not enabled for any core plugins.

You can control whether last variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      borderWidth: ['last'],
    }
  },
}

Odd-child

Add the odd: prefix to only apply a utility when it is an odd-child of its parent. This is mostly useful when elements are being generated in some kind of loop.

<div class="...">
  <div v-for="item in items" class="transform odd:rotate-45 ...">
    {{ item }}
  </div>
</div>

It’s important to note that you should add any odd: utilities to the child element, not the parent element.

By default, the odd-child variant is not enabled for any core plugins.

You can control whether odd variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      backgroundColor: ['odd'],
    }
  },
}

Even-child

Add the even: prefix to only apply a utility when it is an even-child of its parent. This is mostly useful when elements are being generated in some kind of loop.

<div class="...">
  <div v-for="item in items" class="transform even:rotate-45 ...">
    {{ item }}
  </div>
</div>

It’s important to note that you should add any even: utilities to the child element, not the parent element.

By default, the even-child variant is not enabled for any core plugins.

You can control whether even variants are enabled for a plugin in the variants section of your tailwind.config.js file:

// tailwind.config.js
module.exports = {
  // ...
  variants: {
    extend: {
      backgroundColor: ['even'],
    }
  },
}

Combining with responsive prefixes

State variants are also responsive, meaning you can do things like change an element’s hover style at different breakpoints for example.

To apply a state variant at a specific breakpoint, add the responsive prefix first, before the state prefix:

<button class="... hover:bg-green-500 sm:hover:bg-blue-500">
  <!-- ... -->
</button>

Generating variants for custom utilities

You can generate state variants for your own custom CSS classes by wrapping them with the @variants directive:

/* Input: */
@variants group-hover, hover, focus {
  .banana {
    color: yellow;
  }
}

/* Output: */
.banana {
  color: yellow;
}
.group:hover .group-hover\:banana {
  color: yellow;
}
.hover\:banana:hover {
  color: yellow;
}
.focus\:banana:focus {
  color: yellow;
}

For more information, see the @variants directive documentation.


Creating custom variants

You can create your own variants for any states Tailwind doesn’t support by default by writing a custom variant plugin.

For example, this simple plugin adds support for the required pseudo-class variant:

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addVariant, e }) {
      addVariant('required', ({ modifySelectors, separator }) => {
        modifySelectors(({ className }) => {
          return `.${e(`required${separator}${className}`)}:required`
        })
      })
    })
  ]
}

Learn more about writing variant plugins in the variant plugin documentation.


Default variants reference

Due to file-size considerations, Tailwind does not include all variants for all utilities by default.

To configure which variants are enabled for your project, see the configuring variants documentation.

// Default configuration
module.exports = {
  // ...
  variants: {
    accessibility: ['responsive', 'focus-within', 'focus'],
    alignContent: ['responsive'],
    alignItems: ['responsive'],
    alignSelf: ['responsive'],
    animation: ['responsive'],
    appearance: ['responsive'],
    backdropBlur: ['responsive'],
    backdropBrightness: ['responsive'],
    backdropContrast: ['responsive'],
    backdropFilter: ['responsive'],
    backdropGrayscale: ['responsive'],
    backdropHueRotate: ['responsive'],
    backdropInvert: ['responsive'],
    backdropOpacity: ['responsive'],
    backdropSaturate: ['responsive'],
    backdropSepia: ['responsive'],
    backgroundAttachment: ['responsive'],
    backgroundBlendMode: ['responsive'],
    backgroundClip: ['responsive'],
    backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
    backgroundImage: ['responsive'],
    backgroundOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
    backgroundPosition: ['responsive'],
    backgroundRepeat: ['responsive'],
    backgroundSize: ['responsive'],
    backgroundOrigin: ['responsive'],
    blur: ['responsive'],
    borderCollapse: ['responsive'],
    borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
    borderOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
    borderRadius: ['responsive'],
    borderStyle: ['responsive'],
    borderWidth: ['responsive'],
    boxDecorationBreak: ['responsive'],
    boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
    boxSizing: ['responsive'],
    brightness: ['responsive'],
    clear: ['responsive'],
    container: ['responsive'],
    contrast: ['responsive'],
    cursor: ['responsive'],
    display: ['responsive'],
    divideColor: ['responsive', 'dark'],
    divideOpacity: ['responsive', 'dark'],
    divideStyle: ['responsive'],
    divideWidth: ['responsive'],
    dropShadow: ['responsive'],
    fill: ['responsive'],
    filter: ['responsive'],
    flex: ['responsive'],
    flexDirection: ['responsive'],
    flexGrow: ['responsive'],
    flexShrink: ['responsive'],
    flexWrap: ['responsive'],
    float: ['responsive'],
    fontFamily: ['responsive'],
    fontSize: ['responsive'],
    fontSmoothing: ['responsive'],
    fontStyle: ['responsive'],
    fontVariantNumeric: ['responsive'],
    fontWeight: ['responsive'],
    gap: ['responsive'],
    gradientColorStops: ['responsive', 'dark', 'hover', 'focus'],
    grayscale: ['responsive'],
    gridAutoColumns: ['responsive'],
    gridAutoFlow: ['responsive'],
    gridAutoRows: ['responsive'],
    gridColumn: ['responsive'],
    gridColumnEnd: ['responsive'],
    gridColumnStart: ['responsive'],
    gridRow: ['responsive'],
    gridRowEnd: ['responsive'],
    gridRowStart: ['responsive'],
    gridTemplateColumns: ['responsive'],
    gridTemplateRows: ['responsive'],
    height: ['responsive'],
    hueRotate: ['responsive'],
    inset: ['responsive'],
    invert: ['responsive'],
    isolation: ['responsive'],
    justifyContent: ['responsive'],
    justifyItems: ['responsive'],
    justifySelf: ['responsive'],
    letterSpacing: ['responsive'],
    lineHeight: ['responsive'],
    listStylePosition: ['responsive'],
    listStyleType: ['responsive'],
    margin: ['responsive'],
    maxHeight: ['responsive'],
    maxWidth: ['responsive'],
    minHeight: ['responsive'],
    minWidth: ['responsive'],
    mixBlendMode: ['responsive'],
    objectFit: ['responsive'],
    objectPosition: ['responsive'],
    opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
    order: ['responsive'],
    outline: ['responsive', 'focus-within', 'focus'],
    overflow: ['responsive'],
    overscrollBehavior: ['responsive'],
    padding: ['responsive'],
    placeContent: ['responsive'],
    placeItems: ['responsive'],
    placeSelf: ['responsive'],
    placeholderColor: ['responsive', 'dark', 'focus'],
    placeholderOpacity: ['responsive', 'dark', 'focus'],
    pointerEvents: ['responsive'],
    position: ['responsive'],
    resize: ['responsive'],
    ringColor: ['responsive', 'dark', 'focus-within', 'focus'],
    ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'],
    ringOffsetWidth: ['responsive', 'focus-within', 'focus'],
    ringOpacity: ['responsive', 'dark', 'focus-within', 'focus'],
    ringWidth: ['responsive', 'focus-within', 'focus'],
    rotate: ['responsive', 'hover', 'focus'],
    saturate: ['responsive'],
    scale: ['responsive', 'hover', 'focus'],
    sepia: ['responsive'],
    skew: ['responsive', 'hover', 'focus'],
    space: ['responsive'],
    stroke: ['responsive'],
    strokeWidth: ['responsive'],
    tableLayout: ['responsive'],
    textAlign: ['responsive'],
    textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
    textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
    textOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
    textOverflow: ['responsive'],
    textTransform: ['responsive'],
    transform: ['responsive'],
    transformOrigin: ['responsive'],
    transitionDelay: ['responsive'],
    transitionDuration: ['responsive'],
    transitionProperty: ['responsive'],
    transitionTimingFunction: ['responsive'],
    translate: ['responsive', 'hover', 'focus'],
    userSelect: ['responsive'],
    verticalAlign: ['responsive'],
    visibility: ['responsive'],
    whitespace: ['responsive'],
    width: ['responsive'],
    wordBreak: ['responsive'],
    zIndex: ['responsive', 'focus-within', 'focus']
  }
}