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.
<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 a child element has 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-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: {
translate: ['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: {
translate: ['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'],
backgroundAttachment: ['responsive'],
backgroundClip: ['responsive'],
backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
backgroundImage: ['responsive'],
backgroundOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'],
borderCollapse: ['responsive'],
borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
borderOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
borderRadius: ['responsive'],
borderStyle: ['responsive'],
borderWidth: ['responsive'],
boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
boxSizing: ['responsive'],
clear: ['responsive'],
container: ['responsive'],
cursor: ['responsive'],
display: ['responsive'],
divideColor: ['responsive', 'dark'],
divideOpacity: ['responsive'],
divideStyle: ['responsive'],
divideWidth: ['responsive'],
fill: ['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'],
gridAutoColumns: ['responsive'],
gridAutoFlow: ['responsive'],
gridAutoRows: ['responsive'],
gridColumn: ['responsive'],
gridColumnEnd: ['responsive'],
gridColumnStart: ['responsive'],
gridRow: ['responsive'],
gridRowEnd: ['responsive'],
gridRowStart: ['responsive'],
gridTemplateColumns: ['responsive'],
gridTemplateRows: ['responsive'],
height: ['responsive'],
inset: ['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'],
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', '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', 'focus-within', 'focus'],
ringWidth: ['responsive', 'focus-within', 'focus'],
rotate: ['responsive', 'hover', 'focus'],
scale: ['responsive', 'hover', 'focus'],
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', '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']
}
}