Free · Fast · Privacy-first

CSS Hover Animation

Hover effects seem simple until you need to choose between a CSS transition and a CSS animation, or until a multi-step hover sequence reverses incorrectly when the cursor leaves.

Live preview element you can hover directly in the tool

🔒

Side-by-side mode comparing transition and animation approaches

Control over enter and exit sequences independently

Generates complete :hover CSS for both approaches

Cost
Free forever
Sign-up
Not required
Processing
In your browser
Privacy
Files stay local
FreeNo signupWhite-label

Add this Animation Builder to your website

Drop the Animation Builder into any page — blog post, product docs, intranet, school portal — with a single line of HTML. Your visitors get the full tool, processed entirely in their browser. No backend, no uploads, no signup.

  • Files stay 100% in the visitor's browser
  • Responsive — adapts to any container width
  • Free forever, no API key needed

Embed code

<iframe
  src="https://www.fixtools.io/css-tool/animation-builder?embed=1"
  width="100%"
  height="780"
  frameborder="0"
  style="border:0;border-radius:16px;max-width:900px;"
  title="Animation Builder by FixTools"
  loading="lazy"
  allow="clipboard-write"
></iframe>

Attribution-friendly: a small "Powered by FixTools" link appears in the embed footer.

CSS Transition vs CSS Animation for Hover: How They Differ and When to Use Each

A CSS transition interpolates between two states when a property value changes. For hover effects, that change happens when the :hover pseudo-class activates, and the reverse happens when the cursor leaves. The transition is defined on the base element selector, not on the :hover selector: transition: transform 250ms ease-out on the element tells the browser to animate any transform change smoothly. When :hover adds a transform value, the transition plays forward. When :hover is removed, the same transition plays in reverse automatically. This automatic reversal is the defining feature of transitions: they require no separate exit animation and always return cleanly to the base state. A CSS animation by contrast runs a @keyframes sequence independently. The animation can be placed on the :hover selector to start when the cursor enters, but when the cursor leaves and :hover is removed, the animation stops immediately and the element snaps back to its base style unless a separate exit animation is defined.

When to use transitions for hover effects: any single-step property change that should reverse cleanly when the cursor leaves. This covers the vast majority of UI hover effects: colour changes on buttons and links, scale increases on cards, shadow lifts, border-radius changes, and opacity shifts on overlay labels. Transitions handle these with two declarations (one on the base element, one on :hover) and the browser manages the interpolation in both directions with no additional code. The transition approach is correct when the exit should exactly mirror the entrance.

When to use animation on hover: multi-step effects with distinct enter sequences, effects that should complete regardless of whether the cursor stays, or enter and exit sequences that are intentionally different. An example is a button where hovering triggers an underline that sweeps from left to right (not just appearing), and removing hover triggers a separate sweep from right to left. Both sequences require separate @keyframes blocks, and the enter animation is applied on :hover while the exit animation is applied using a class toggled by JavaScript. CSS alone cannot define a separate exit animation for :hover removal; JavaScript is required to add the exit class before the :hover state clears.

Touch device behaviour is the most overlooked factor in hover animation design. Mobile and tablet users have no cursor, and the way mobile browsers expose the :hover state is inconsistent and frequently surprising. iOS Safari triggers :hover on the first tap and clears it on the next tap elsewhere, meaning a hover animation may stick visibly after the user interacts. Android Chrome behaves differently in different versions. The reliable solution is to wrap all hover animation rules in a media query that targets devices with a real pointer: @media (hover: hover) and (pointer: fine). Outside that query, provide tap feedback through :active states or focus-visible styles instead. This separation keeps the desktop experience polished while preventing the sticky-hover bug on touch devices that frustrates mobile users and looks like a stylesheet error.

How to use this tool

💡

Choose transition or animation mode. For transitions, set the property, duration, and easing. For animations, build the @keyframes enter sequence and optionally a separate exit sequence. Hover over the preview to test, then copy the code.

How It Works

Step-by-step guide to css hover animation:

  1. 1

    Choose transition or animation approach

    Select transitions for simple property changes that should reverse on hover-out. Select animations for multi-step hover sequences or effects that should complete regardless of cursor position.

  2. 2

    Define the hover state

    For transitions: set the target property value in the :hover selector and the transition duration and easing on the base selector. For animations: build the @keyframes enter sequence and set it on the :hover selector.

  3. 3

    Test the hover in the live preview

    Hover over the preview element to test the enter sequence. Move the cursor away to test the exit. Verify that the exit is smooth and complete for transitions, or that the animation behaves correctly when interrupted for animations.

  4. 4

    Copy the generated CSS

    Click Copy Code. For animation-based hover effects, add a JavaScript class toggle if you need a custom exit animation sequence separate from the CSS :hover removal snap-back.

Real-world examples

Common situations where this approach makes a real difference:

Product card hover lift

An e-commerce developer adds a card lift using transition: transform 180ms ease-out, box-shadow 180ms ease-out on the card. The :hover state adds translateY(-6px) and an elevated box-shadow. The transition reverses automatically on hover-out. No JavaScript is needed. The 180ms duration matches the speed of a mouse movement, making the card feel responsive to the cursor rather than delayed.

Navigation link underline sweep

A design agency site uses a CSS animation on nav link :hover to sweep an underline from left to right using a scaleX(0) to scaleX(1) @keyframes on a ::after pseudo-element. The enter animation plays in 200ms. On hover-out, a JavaScript mouseleave handler applies a reverse-sweep class that plays the exit animation before removing both classes, giving the underline a directional sweep both in and out.

Icon button scale and colour shift

A dashboard toolbar uses transition: transform 150ms ease-out, color 150ms ease-out on icon buttons. The :hover state adds scale(1.12) and a blue accent colour. The transition approach handles both properties simultaneously with one declaration per property. The 150ms duration makes the response feel immediate, matching the fast interaction tempo of a toolbar used repeatedly throughout a session.

Feature list item reveal

A pricing page feature list uses a hover animation that slides a background highlight from left to right across each list item using a scaleX keyframe on a positioned ::before pseudo-element. The animation plays on :hover and completes in 250ms. Because the cursor may leave before the animation finishes, animation-fill-mode: forwards holds the highlight in the completed state briefly, making the interaction feel deliberate rather than interrupted.

When to use this guide

Use this when building card hover lifts, button colour transitions, link underline reveals, or any multi-step hover sequence that needs to play a specific enter or exit sequence rather than simply reversing.

Pro tips

Get better results with these expert suggestions:

1

Use transition for hover, animation only when the exit needs custom behaviour

The default choice for any hover effect should be a CSS transition. Reach for @keyframes hover animations only when the exit sequence cannot be a simple reversal. Transitions require less code, have no snap-back issue, and are easier for other developers to read and maintain in a shared stylesheet.

2

Apply transition to specific properties, not all

Using transition: all is tempting but dangerous. It causes the browser to interpolate every CSS property change, including unintentional ones triggered by JavaScript or parent state changes. Specify exactly which properties should animate: transition: transform 200ms ease-out, box-shadow 200ms ease-out. This gives predictable behaviour and avoids unexpected transitions when other styles change.

3

Test hover animations at 0.25x speed in DevTools

Open the Animations panel in Chrome DevTools, hover the element, and set playback to 25%. This reveals whether the easing curve is correct at each stage and whether the exit reversal is smooth. Issues that appear as a slight jank at full speed become obvious at slow motion and are much easier to debug before shipping.

4

Account for rapid cursor movement in hover animation design

Users sometimes move the cursor quickly across multiple elements. Test what happens when the cursor enters and exits an element within 50ms, before any animation starts or completes. Transitions handle this cleanly by reversing mid-play. Animations may leave an element in an intermediate state if the class is removed before the @keyframes finishes. Design for rapid mousing by keeping hover animations under 250ms.

FAQ

Frequently asked questions

A transition automatically reverses when the triggering condition is removed. Define it on the base element and it handles both enter and exit with one declaration. A CSS animation on a :hover selector starts when the cursor enters but stops abruptly when the cursor leaves, snapping back to the base state unless a separate exit animation is applied via JavaScript. Transitions are simpler for reversible effects. Animations are necessary for multi-step sequences or directionally distinct enter and exit motions.
When the cursor leaves an element, the :hover pseudo-class is removed immediately. Any animation applied only to the :hover selector stops playing and the element returns to its base CSS state instantly. To create a smooth exit, you need either a CSS transition on the base element (which handles reversal automatically) or a JavaScript event listener that applies an exit animation class on mouseleave, waits for the animation to finish, then removes both classes. The CSS :hover selector alone cannot define a custom exit animation.
Yes, but the behavior may surprise you. animation-fill-mode: forwards holds the element at the last keyframe state after the animation completes. On a hover animation, this means the element stays in the hovered state visually even after the cursor leaves, because the fill-mode overrides the base style. This is useful for effects that should persist after a quick hover, but it means the element never returns to its base state without JavaScript removing the animation or the class. Use forwards intentionally, not as a default.
The transition approach is best for a card lift: add transition: transform 200ms ease-out, box-shadow 200ms ease-out to the card base selector. In the :hover selector, add transform: translateY(-4px) and the elevated box-shadow value. The card lifts when hovered and settles back when not, with no @keyframes needed. The ease-out easing makes the lift feel immediate and the settle feel natural. A duration of 150 to 200ms feels snappy; 300ms or more starts to feel slow for a hover interaction.
Place the animation property only in the :hover selector rather than on the base element selector. The browser only applies and starts the animation when the :hover pseudo-class activates, meaning the element shows no animation on initial render. An important caveat: if the same @keyframes name is referenced in both the base and :hover selectors, the base animation will play on load. Use distinct @keyframes names for hover-only animations to avoid accidental load-time triggering.
Ease-out is the standard choice for hover enter transitions: the element moves quickly at the start and settles at the target state, which feels responsive to the cursor's arrival. For hover exit transitions (when the cursor leaves), ease-in feels natural: the element starts slowly and accelerates away from the hover state, as if retreating. Some designers specify different easing for enter and exit using separate transition declarations, though most production code uses the same ease-out for both for simplicity.
Hover interactions on touch devices behave differently from pointer devices. A touch "hover" (the first tap on an element) may trigger the :hover state in some browsers but not others. Use the @media (hover: hover) and (pointer: fine) media queries to apply hover animations only to devices with a true pointer device. Inside that query, define all :hover animation rules. Outside the query, provide alternative touch interaction feedback such as :active state transitions that respond to touch events. This separation also stops the sticky-hover bug on iOS Safari where a tap can leave the hover state applied until the user taps elsewhere, which looks like a stylesheet bug to mobile users.
Both approaches have equivalent performance when animating transform and opacity. Both run on the compositor thread for those properties. The practical performance difference emerges with properties like box-shadow, color, and background-color: both transitions and animations trigger paint when cycling these, but a CSS transition fires paint only during the transition duration, while a poorly designed infinite animation on :hover would fire paint continuously while hovered. For hover effects, transitions on layout and paint-triggering properties are slightly lower risk because they have a defined end point. As a rule of thumb, prefer transitions for hover unless you have a specific structural reason to use @keyframes, since transitions impose fewer ways to accidentally introduce layout thrashing or paint storms during interaction.
The sweet spot for hover transition duration is 150 to 250ms. Below 100ms the animation feels like an instant snap with no perceivable motion, defeating the purpose of the transition. Above 350ms the cursor often leaves the element before the animation completes, leaving the user unsure whether the interaction registered. Pair the duration with ease-out easing so the visible motion happens at the start of the cursor arrival, where the user is paying attention. For multi-property transitions like transform plus box-shadow, use the same duration for both so the two changes feel like a single coordinated event rather than two separate animations playing in parallel out of sync.
Yes. While hover animations are typically short and might seem harmless, the prefers-reduced-motion query exists precisely because some users experience symptoms even from brief motion. Wrap hover transitions and animations in @media (prefers-reduced-motion: no-preference) so they only apply when the user has not requested reduced motion. The reduced-motion fallback should preserve the colour change, shadow change, or other static-visible feedback but skip the animated interpolation. This pattern keeps the interface accessible without removing the useful visual feedback that distinguishes a hovered element from its neighbours, which is the actual functional purpose of the hover state.

Related guides

More use-case guides for the same tool:

Ready to get started?

Open the full Animation Builder — free, no account needed, works on any device.

Open Animation Builder →

Free · No account needed · Works on any device