---
title: "Framer Animations & Hover Effects: A Practical Guide (With Code)"
description: "How to build Framer animations and hover effects, from native canvas interactions to code overrides, with real code snippets for buttons, cards, and scroll-based motion."
canonical_url: "https://framerhub.io/blog/framer-animations-hover-effects"
last_updated: "2026-08-07T00:00:00.000Z"
---

Good **framer animations** make an interface feel alive without the visitor consciously noticing why. Bad ones are the opposite: a hover effect that fights the cursor, a scroll animation that jitters, a button that feels sluggish. The difference usually comes down to a handful of practical decisions, not raw animation skill.

This guide covers Framer's native animation tools, when to reach for code overrides instead, and real code you can drop into a project for button hover effects, card interactions, and scroll-based motion.

---

## Native Framer animation: what you get without code

Every layer in Framer has an Effects and Interactions panel covering three core animation types.

### Appear animations

Trigger when an element enters the viewport on scroll: fade in, slide up, scale in. Configurable delay and stagger make these useful for sequential reveals (a row of cards appearing one after another) without writing anything.

### Hover and tap states

Set a different visual state for hover and tap/click directly on the layer, with a duration and easing curve. This covers most button and card hover effects without touching code.

### Scroll-linked animations

Bind a property (opacity, scale, position) to scroll progress, so an element transforms as the visitor scrolls through a section. Framer's native scroll effects panel covers common patterns like parallax and scroll-triggered reveals.

For straightforward cases, a button that scales slightly on hover, a card that lifts with a shadow, a section that fades in on scroll, native tools are the right choice. They're faster to set up and don't require touching code.

---

## When to reach for a code override instead

Code overrides make sense when you need:

- **Custom spring physics** — a specific stiffness/damping combination the native panel doesn't expose
- **Sequenced or staggered animations with logic** — "animate these three elements in order, with a condition based on state"
- **Scroll-linked value mapping** — translating scroll progress into a custom range rather than a fixed native effect
- **Reusable animation behavior across many components** — write once, apply the same override to any layer

If you're not sure which to use, start with native tools. Only move to a code override once you hit something the visual panel genuinely can't do.

---

## Button hover effects with code: real examples

Framer's code override system uses functions that wrap a component and modify its props, built on the [Motion](https://motion.dev) animation library.

### A scale-and-lift hover effect

```tsx
import type { ComponentType } from "react"

export function withHoverLift(Component): ComponentType {
    return (props) => {
        return (
            <Component
                {...props}
                whileHover={{
                    scale: 1.04,
                    boxShadow: "0px 12px 24px rgba(0,0,0,0.15)"
                }}
                whileTap={{ scale: 0.97 }}
                transition={{ type: "spring", stiffness: 400, damping: 17 }}
            />
        )
    }
}
```

Apply this override to any button or card layer from the code panel. The spring transition (rather than a fixed duration ease) is what makes the interaction feel physical instead of mechanical.

### A color-shift hover effect

```tsx
import type { ComponentType } from "react"
import { useState } from "react"

export function withHoverColorShift(Component): ComponentType {
    return (props) => {
        const [hovered, setHovered] = useState(false)

        return (
            <Component
                {...props}
                onHoverStart={() => setHovered(true)}
                onHoverEnd={() => setHovered(false)}
                animate={{
                    backgroundColor: hovered ? "#111111" : "#F5F5F5",
                    color: hovered ? "#FFFFFF" : "#111111"
                }}
                transition={{ duration: 0.25, ease: "easeOut" }}
            />
        )
    }
}
```

Useful for buttons or nav items where a full-property color transition looks more intentional than Framer's default state-swap.

### A magnetic hover effect (cursor-follow)

```tsx
import type { ComponentType } from "react"
import { useRef } from "react"
import { useMotionValue, useSpring } from "framer-motion"

export function withMagneticHover(Component): ComponentType {
    return (props) => {
        const ref = useRef(null)
        const x = useMotionValue(0)
        const y = useMotionValue(0)
        const springX = useSpring(x, { stiffness: 300, damping: 20 })
        const springY = useSpring(y, { stiffness: 300, damping: 20 })

        function handleMouseMove(e) {
            const rect = ref.current.getBoundingClientRect()
            x.set((e.clientX - rect.left - rect.width / 2) * 0.3)
            y.set((e.clientY - rect.top - rect.height / 2) * 0.3)
        }

        function handleMouseLeave() {
            x.set(0)
            y.set(0)
        }

        return (
            <Component
                {...props}
                ref={ref}
                onMouseMove={handleMouseMove}
                onMouseLeave={handleMouseLeave}
                style={{ x: springX, y: springY }}
            />
        )
    }
}
```

This is the "button that follows your cursor slightly" effect common on agency and portfolio sites. The spring smoothing keeps it feeling fluid instead of jumpy.

---

## Scroll-triggered animation with code

For animations tied to scroll progress beyond what the native scroll effects panel covers:

```tsx
import type { ComponentType } from "react"
import { useScroll, useTransform } from "framer-motion"

export function withScrollFade(Component): ComponentType {
    return (props) => {
        const { scrollYProgress } = useScroll()
        const opacity = useTransform(scrollYProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0])
        const y = useTransform(scrollYProgress, [0, 0.2], [40, 0])

        return <Component {...props} style={{ opacity, y }} />
    }
}
```

This maps scroll position to both opacity and vertical offset, so an element fades and settles into place rather than snapping in. Adjust the input range values to control exactly where in the scroll the animation starts and ends.

---

## Card and image hover effects

A common pattern on portfolio and agency sites: an image that zooms slightly on hover, contained within a fixed frame so it doesn't affect surrounding layout.

```tsx
import type { ComponentType } from "react"

export function withImageZoom(Component): ComponentType {
    return (props) => {
        return (
            <Component
                {...props}
                style={{ overflow: "hidden" }}
                whileHover="hover"
                initial="rest"
            >
                <Component.Image
                    variants={{
                        rest: { scale: 1 },
                        hover: { scale: 1.08 }
                    }}
                    transition={{ duration: 0.4, ease: [0.22, 1, 0.36, 1] }}
                />
            </Component>
        )
    }
}
```

The key detail is `overflow: hidden` on the container. Without it, the zoomed image spills past its frame instead of feeling contained within the card.

### Staggered list reveal

For a row or grid of cards animating in with a slight delay between each, rather than all at once:

```tsx
import type { ComponentType } from "react"

export function withStaggerChild(index: number): (Component) => ComponentType {
    return (Component) => (props) => (
        <Component
            {...props}
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.4, delay: index * 0.08, ease: "easeOut" }}
        />
    )
}
```

Apply `withStaggerChild(0)`, `withStaggerChild(1)`, `withStaggerChild(2)` to sequential cards in a row. The `delay: index * 0.08` staggers each card's entrance by roughly 80ms, and `viewport={{ once: true }}` keeps it from re-triggering every time the visitor scrolls back up.

---

## Performance: which properties are safe to animate

Not all animated properties cost the same. This matters more as a page has more simultaneous animations, especially on mobile.

**Cheap to animate (compositor-only, stay smooth):**

- `transform` (scale, translate, rotate)
- `opacity`

**Expensive to animate (trigger layout recalculation):**

- `width` / `height`
- `top` / `left` / `right` / `bottom` (when not using transform)
- `box-shadow` (moderate cost, use sparingly on many simultaneous elements)

Practical rule: build hover and scroll effects around `scale`, `translate` (x/y), and `opacity` wherever the visual goal allows it. Reserve layout-affecting properties for cases where there's genuinely no transform-based alternative. For more on keeping a Framer site fast under real animation load, see our [performance optimization guide](/blog/optimize-framer-site-performance).

---

## Common hover and animation mistakes

**Animating too many elements at once.** A page where every card, button, and image animates independently on scroll reads as chaotic rather than polished. Pick two or three moments that matter and let the rest of the page stay calm.

**Ignoring reduced-motion preferences.** Some visitors have motion sensitivity settings enabled at the OS level. Respect `prefers-reduced-motion` for anything beyond subtle hover states:

```tsx
import type { ComponentType } from "react"
import { useReducedMotion } from "framer-motion"

export function withRespectfulMotion(Component): ComponentType {
    return (props) => {
        const shouldReduceMotion = useReducedMotion()

        return (
            <Component
                {...props}
                whileHover={shouldReduceMotion ? {} : { scale: 1.05 }}
                transition={{ duration: shouldReduceMotion ? 0 : 0.3 }}
            />
        )
    }
}
```

This keeps the interaction functional (the button still responds) while skipping the motion itself for visitors who've asked their system to minimize it.

**Springs that are too bouncy.** High stiffness with low damping creates an overshoot that reads as unpolished on professional sites. Start conservative (stiffness 300-400, damping 20-30) and adjust from there.

**Hover effects with no tap state on mobile.** Hover doesn't exist on touch devices. Make sure buttons have a visible tap/active state so mobile visitors get feedback too.

**Mismatched easing across a page.** Mixing a bouncy spring on one section with a linear ease on another makes a site feel inconsistent, even if each individual animation looks fine in isolation. Pick one or two easing signatures (a spring for interactive elements, a smooth ease-out for scroll reveals) and reuse them consistently.

**Forgetting to test on a real mid-range device.** A stack of scroll-linked animations that feels buttery on a new laptop can visibly stutter on a mid-range phone. If the site targets a general audience rather than a design-forward niche, test on hardware that isn't your best device.

---

## Building animated components without writing overrides yourself

If you don't want to write and maintain code overrides for every project, [FramerHub Components](/plugins/framer-components) includes 90+ pre-built components with animation and hover behavior already configured: animated buttons, hover cards, stat counters, and scroll-triggered sections, all restylable from Framer's native property panel. Browse the full library at [FramerHub Components](/components) to see what's available before writing custom overrides from scratch.

---

## FAQ

### Can you add animations in Framer without code?

Yes. Framer's native interactions panel supports hover, tap, appear, and scroll-triggered animations with no code, using the Effects tab on any layer. Code overrides become useful when you need custom easing curves, physics-based springs, or logic-driven animations (like a counter or a sequence) that go beyond what the visual panel exposes.

### How do I add a hover effect to a button in Framer?

Select the button layer, open the Interactions panel, and add a hover state with your desired property changes (scale, color, shadow). For finer control over timing and physics, wrap the component in a code override that sets `whileHover` and `whileTap` properties with a custom transition.

### What's the difference between Framer's native animations and code overrides?

Native animations are configured visually in the Effects and Interactions panels and cover most common cases: hover, tap, appear, scroll-triggered reveals. Code overrides are TypeScript functions that wrap a component and give you programmatic control: custom springs, sequenced animations, scroll-linked value mapping, and conditional logic that the visual panel doesn't support.

### Do heavy animations slow down a Framer site?

They can. Animating properties like width, height, or box-shadow triggers layout recalculation and is more expensive than animating transform and opacity, which run on the compositor and stay smooth even on lower-end devices. Favor transform-based animations (scale, translate, rotate) over layout-triggering ones whenever possible.

### What is a Framer code override?

A code override is a TypeScript function you write in Framer's code editor that wraps a component and modifies its props, adding animation behavior, state, or event handlers. Framer applies the override directly to a layer on the canvas without changing the component's own code, so the same base component can have different override behavior in different projects.

---

## Bottom line

Native Framer animation tools cover most hover and scroll effects without touching code. Reach for a code override when you need custom physics, sequencing, or scroll-linked logic the visual panel doesn't expose, and favor transform/opacity over layout-triggering properties for anything that needs to stay smooth.

If you'd rather use animated components that are already built and tested than write and maintain overrides project by project, [FramerHub Components](/plugins/framer-components) covers the most common patterns out of the box. Full documentation on Framer's override system and animation API lives at [framer.com/developers](https://www.framer.com/developers/).

*Last updated: August 2026*
