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.
-
:hoveris 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
transitionfor 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 attentionTip:
cursor: pointeris 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 onceHover: pulse foreverHover: 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:
- Wrap the trigger text in a container.
- Create a tooltip element inside it.
- Hide it by default (
opacity: 0, maybetransform). - 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: inlinetoinline-blockorinline-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;
}
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 cardThis 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
:hoverto style elements while the pointer is over them. -
Add
cursor: pointerto clickable elements (not everything). -
Put
transitionon the base selector, not inside:hover. -
Use
transformandopacityfor smooth, performant hover effects. -
Use
::afterfor 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!
