What is :hover in CSS?

:hover is a CSS pseudo-class that matches an element when the user’s pointing device (usually a mouse or trackpad cursor) is positioned over it. In plain words: it lets you style something “when it’s being hovered”.

You’ll often use :hover for buttons, links, cards, menus, images, and anything that benefits from a little “yes, you can click me” feedback.

  • :hover is state-based (not time-based). It applies while the element is hovered.
  • Hover isn’t guaranteed on touch devices. Many phones/tablets don’t have hover the same way desktop does.
  • Hover is often paired with transition for smooth effects.

Your first hover: change color and cursor

Let’s start with the classics: a color change and the “pointer” cursor. If something is clickable, it should usually look clickable.

a:hover {
  color: #0a58ca;
  cursor: pointer;
}
    
a:hover {
  color: #c1121f;
  cursor: pointer;
}
    
a:hover {
  color: #198754;
  cursor: pointer;
}
    


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.demo {
display: grid;
gap: 14px;
max-width: 560px;
}

a {
color: #111;
text-decoration: none;
font-weight: 600;
}

.note {
font-size: 14px;
color: #444;
} 


Hover me: I’m a link Hover me too: I also want attention

Tip: cursor: pointer is common for clickable UI, but don’t use it on things that aren’t actually clickable.

CSS hover pointer: when to use cursor: pointer

Use cursor: pointer for actual interactive elements (buttons, links, clickable cards). Avoid using it on random elements—otherwise users will try to click everything (and then blame you).

CSS hover effects (the fun stuff)

“Hover effects” usually mean small visual changes: background, border, shadow, transform, filter, and so on. The best hover effects are subtle, fast, and consistent.

.card:hover {
  background: #f6f6f6;
  border-color: #111;
}
    
.card:hover {
  transform: translateY(-6px);
}
    
.card:hover {
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18);
}
    
.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18);
}
    


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
max-width: 720px;
margin: 20px;
}

.card {
padding: 16px;
border: 2px solid #d9d9d9;
border-radius: 14px;
background: #fff;
}

.card h4 {
margin: 0 0 8px 0;
}

.card p {
margin: 0;
color: #444;
line-height: 1.4;
} 


Card A

Hover me. I’ll react. I’m dramatic like that.

Card B

Same markup, different hover snippet.

CSS hover transition: smooth hover effects

The most common beginner mistake is putting transition inside :hover. It can work in one direction, but it’s not what you want.

Put transition on the base element (the non-hover state), so it animates both when you hover in and when you hover out.

.button {
  transition: color 200ms ease, background 200ms ease, transform 200ms ease;
}

.button:hover {
color: #fff;
background: #111;
transform: translateY(-3px);
} 
.button {
transition: transform 140ms ease, box-shadow 140ms ease;
}

.button:hover {
transform: translateY(-3px);
box-shadow: 0 12px 22px rgba(0, 0, 0, 0.22);
} 
.button {
transition: letter-spacing 220ms ease, background 220ms ease;
}

.button:hover {
background: #0a58ca;
letter-spacing: 0.06em;
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.row {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
margin: 20px;
}

.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 16px;
border-radius: 12px;
border: 2px solid #111;
background: #fff;
color: #111;
font-weight: 700;
cursor: pointer;
user-select: none;
} 


Transition tip: animate only what you need

Prefer transition: transform ... and opacity when possible. They’re usually smooth and performant. Avoid animating layout-heavy things (like width) unless you really need it.

Learn more about transitions in the CSS Transition Interactive Tutorial.

CSS hover animation: keyframes on hover

Transitions are perfect for “A to B” changes. Animations are better when you want a repeating motion (wiggle, pulse, bounce) while hovered.

.badge:hover {
  animation: wiggle 410ms ease-in-out;
}

@keyframes wiggle {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(-6deg);
}
50% {
transform: rotate(6deg);
}
75% {
transform: rotate(-4deg);
}
100% {
transform: rotate(0deg);
}
} 
.badge:hover {
animation: pulse 700ms ease-in-out infinite;
}

@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.06);
}
100% {
transform: scale(1);
}
} 
.badge:hover {
animation: floaty 900ms ease-in-out infinite;
}

@keyframes floaty {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-6px);
}
100% {
transform: translateY(0px);
}
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.wrap {
display: grid;
gap: 14px;
max-width: 520px;
margin: 20px;
}

.badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 16px;
border-radius: 999px;
border: 2px solid #111;
font-weight: 800;
width: fit-content;
cursor: pointer;
user-select: none;
} 


Hover: wiggle once
Hover: pulse forever
Hover: float forever

Try switching between the three code snippets then hovering to see them all in action.

Learn more about animations in the CSS Animation Interactive Tutorial.

Animation accessibility: reduce motion when users ask

Some users prefer less motion. You can respect that with prefers-reduced-motion. Keep it in mind for hover animations.

@media (prefers-reduced-motion: reduce) {
  .badge {
    transition: none;
    animation: none;
  }

.badge:hover {
animation: none;
}
} 
@media (prefers-reduced-motion: reduce) {
.badge:hover {
transform: none;
}
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 16px;
border-radius: 999px;
border: 2px solid #111;
font-weight: 800;
cursor: pointer;
user-select: none;
transition: transform 160ms ease;
margin: 20px;
}

.badge:hover {
transform: translateY(-4px);
} 


Hover me (and consider reduced motion)

Here, users who prefer reduced motion won’t see the hover animation, making the experience more comfortable for them.

CSS hover text: hovering text and typography

Hovering text can mean a lot of things: underline on hover, highlight background, change font weight, reveal hidden text, or add a little “marker” effect.

.link:hover {
  text-decoration: underline;
}
    
.link:hover {
  background: #fff3cd;
}
    
.link:hover {
  font-weight: 800;
}
    
.link:hover .extra {
  opacity: 1;
  transform: translateY(0px);
}
    


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.link {
display: inline-flex;
gap: 10px;
align-items: baseline;
padding: 6px 8px;
border-radius: 10px;
color: #111;
text-decoration: none;
cursor: pointer;
}

.extra {
font-size: 14px;
color: #444;
opacity: 0;
transform: translateY(6px);
transition: opacity 180ms ease, transform 180ms ease;
} 


Hover this text (surprise text appears)

CSS hover color: changing colors on hover

Color changes are a simple and effective hover cue. You can change text color, background color, borders, and even SVG icon colors (if they inherit currentColor).

.pill:hover {
  color: #fff;
  background: #111;
}
    
.pill:hover {
  color: #fff;
  background: #0a58ca;
}
    
.pill:hover {
  color: #111;
  background: #ffd166;
}
    


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.pill {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: 999px;
border: 2px solid #111;
background: #fff;
color: #111;
font-weight: 800;
cursor: pointer;
user-select: none;
transition: background 180ms ease, color 180ms ease;
margin: 20px;
} 


Hover: color + background

CSS hover ::after: decorations and effects

::after is a pseudo-element that creates a “virtual element” inside your element. It’s perfect for decorative lines, underlines, badges, and little flourish effects—without extra HTML.

A common pattern is: create the ::after element in the base state, then animate it on hover. That way the browser always knows it exists and can transition smoothly.

.fancy::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 8px;
  height: 3px;
  background: #111;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 180ms ease;
}

.fancy:hover::after {
transform: scaleX(1);
} 
.fancy::after {
content: "";
position: absolute;
left: 10px;
right: 10px;
bottom: 8px;
height: 3px;
background: #0a58ca;
transform: scaleX(0);
transform-origin: center;
transition: transform 200ms ease;
}

.fancy:hover::after {
transform: scaleX(1);
} 
.fancy::after {
content: "NEW";
position: absolute;
right: -10px;
top: -10px;
font-size: 12px;
font-weight: 900;
padding: 6px 10px;
border-radius: 999px;
background: #c1121f;
color: #fff;
transform: translateY(-6px);
opacity: 0;
transition: transform 180ms ease, opacity 180ms ease;
}

.fancy:hover::after {
transform: translateY(0px);
opacity: 1;
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.fancy {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 18px;
border: 2px solid #111;
border-radius: 14px;
font-weight: 900;
cursor: pointer;
user-select: none;
background: #fff;
margin: 20px;
} 


Hover me

Tip: content is required

Pseudo-elements like ::after and ::before don’t appear unless you set content. Even if you want a purely decorative box, you still need content: "";.

Learn more in the CSS Pseudo Elements Interactive Tutorial.

CSS hover tooltip (pure CSS)

Tooltips are a classic hover use case. The pattern is:

  1. Wrap the trigger text in a container.
  2. Create a tooltip element inside it.
  3. Hide it by default (opacity: 0, maybe transform).
  4. Reveal it on :hover.

This is a beginner-friendly approach, and it works without JavaScript. (For full accessibility, you’ll typically also support keyboard focus, but we’ll keep this hover-first and mention the upgrade.)

.tip:hover .tooltip {
  opacity: 1;
  transform: translate(-50%, -10px);
  pointer-events: auto;
}
    
.tip:hover .tooltip {
  opacity: 1;
  transform: translate(-50%, -10px);
}

.tip:hover .tooltip::after {
transform: translateX(-50%) rotate(45deg);
} 
.tip:hover .tooltip, .tip:focus-within .tooltip {
opacity: 1;
transform: translate(-50%, -10px) scale(1);
}

.tooltip {
transform: translate(-50%, 0px) scale(0.96);
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.tip {
position: relative;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 12px;
border: 2px solid #111;
cursor: help;
user-select: none;
font-weight: 800;
background: #fff;
margin: 110px;
font-size: 20px;
}

.tooltip {
position: absolute;
left: 50%;
bottom: calc(100% + 10px);
width: 240px;
padding: 10px 12px;
border-radius: 12px;
background: #111;
color: #fff;
font-weight: 600;
font-size: 14px;
line-height: 1.35;
opacity: 0;
pointer-events: none;
transform: translate(-50%, 0px);
transition: opacity 160ms ease, transform 160ms ease;
}

.tooltip::after {
content: "";
position: absolute;
left: 50%;
top: calc(100% - 6px);
width: 12px;
height: 12px;
background: #111;
transform: translateX(-50%) rotate(45deg);
} 



    

Tooltip upgrade: support keyboard focus too

A helpful improvement is to show the tooltip on :focus-within as well, so keyboard users can trigger it. You can do that by changing .tip:hover .tooltip to .tip:hover .tooltip, .tip:focus-within .tooltip.

Hover hit area: inline vs inline-block

Sometimes hover feels “fussy” because the element you’re hovering is tiny (like a small text node). You can make hover easier by:

  • Adding padding to increase the hit area
  • Switching from display: inline to inline-block or inline-flex
  • Applying hover styles to a parent container (like a whole card)
.inline:hover {
  background: #fff3cd;
}

.inline-block {
  display: inline-block;
  padding: 6px 10px;
}

.inline-block:hover {
background: #fff3cd;
}

.blocky {
display: inline-flex;
padding: 10px 12px;
border-radius: 12px;
border: 2px solid #111;
}

.blocky:hover {
background: #f6f6f6;
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

p {
max-width: 680px;
line-height: 1.5;
}

.inline,
.inline-block,
.blocky {
font-weight: 800;
cursor: pointer;
} 


This is inline hover (small hit area).

This is inline-block hover (bigger, nicer).

This is inline-flex hover (feels like a UI component).

Learn more in the CSS Display Inline-Block Interactive Tutorial and the CSS Display Inline-Flex Interactive Tutorial.

Transition vs animation: when to use which

If you’re deciding between transition and animation, here’s a simple rule:

  • Use transition when you have a start and end state (hovered vs not hovered).
  • Use animation when you want movement over time (looping or multi-step motion).

Mini pattern: image hover overlay

Here’s a very common hover effect: a subtle overlay + text that appears when hovering an image. This is great for thumbnails, portfolio items, or “click to view” content.

.thumb:hover .overlay {
  opacity: 1;
}

.thumb:hover .label {
transform: translateY(0px);
opacity: 1;
} 
.thumb:hover img {
transform: scale(1.06);
} 
.thumb:hover .overlay {
opacity: 1;
}

.thumb:hover img {
transform: scale(1.06);
}

.thumb:hover .label {
transform: translateY(0px);
opacity: 1;
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.thumb {
position: relative;
width: 360px;
aspect-ratio: 16 / 9;
border-radius: 18px;
overflow: hidden;
border: 2px solid #111;
cursor: pointer;
}

.thumb img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transform: scale(1);
transition: transform 220ms ease;
}

.overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
opacity: 0;
transition: opacity 180ms ease;
}

.label {
position: absolute;
left: 14px;
bottom: 14px;
padding: 10px 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.9);
font-weight: 900;
opacity: 0;
transform: translateY(10px);
transition: opacity 180ms ease, transform 180ms ease;
} 


Random placeholder image
View details

Common hover pitfalls and best practices

Pitfall: putting transition inside :hover

If you put transition only inside the hover selector, you’ll often get an “animate in, snap out” effect. Put transition on the base selector instead.

Pitfall: hover on touch devices

Touch devices can behave differently with hover. Some simulate hover on tap, some don’t. Your UI should still make sense without hover.

A practical approach is to treat hover as a “extra polish”, not the only way to reveal critical information.

Best practice: check hover support when needed

If you’re building complex hover-only UI, consider using a media query that checks if hover is available. That way, your hover behavior can be desktop-only, while touch gets a simpler experience.

@media (hover: hover) {
  .card:hover {
    transform: translateY(-6px);
  }
}
    
@media (hover: none) {
  .hint {
    display: block;
  }
}
    


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.card {
width: 410px;
padding: 16px;
border: 2px solid #111;
border-radius: 16px;
background: #fff;
transition: transform 180ms ease;
margin: 20px;
}

.hint {
display: none;
margin-top: 10px;
font-size: 14px;
color: #444;
} 


Hover-aware card
This hint can appear on devices that don’t support hover.

Mini pattern: a button hover you can reuse everywhere

Let’s wrap up with a “good default” button hover: clear pointer cursor, smooth transition, subtle lift, and a color shift. It’s friendly, modern, and doesn’t scream at the user.

.button {
  transition: transform 160ms ease, background 160ms ease, color 160ms ease;
}

.button:hover {
transform: translateY(-3px);
background: #111;
color: #fff;
} 
.button {
transition: transform 160ms ease, box-shadow 160ms ease;
}

.button:hover {
transform: translateY(-3px);
box-shadow: 0 14px 26px rgba(0, 0, 0, 0.22);
} 
.button {
transition: background 160ms ease;
}

.button:hover {
background: #0a58ca;
} 


*,
::before,
::after {
box-sizing: border-box;
}

body {
font-family: system-ui, Arial, sans-serif;
padding: 20px;
}

.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 16px;
border-radius: 14px;
border: 2px solid #111;
background: #fff;
color: #111;
font-weight: 900;
cursor: pointer;
user-select: none;
margin: 20px;
font-size: 20px;
} 


 

Recap: :hover cheat sheet

  • Use :hover to style elements while the pointer is over them.
  • Add cursor: pointer to clickable elements (not everything).
  • Put transition on the base selector, not inside :hover.
  • Use transform and opacity for smooth, performant hover effects.
  • Use ::after for decorative hover effects (underlines, badges) without extra HTML.
  • Tooltips can be done with pure CSS: hide by default, reveal on hover.
  • Remember touch devices: hover should be “nice to have”, not “required to use the site”.

CSS Hover Conclusion

Hover is a powerful tool for adding interactivity and feedback to your web designs. With a little practice, you can create engaging hover effects that delight users and enhance the experience. Remember to keep it subtle, performant, and accessible—and have fun experimenting!