# Framer Motion Spring Presets: Stiffness and Damping

> Four framer motion spring presets with real stiffness and damping values, the UI each one fits, and how to share them across a React component library.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 13, 2026
- Category: [Component guides](https://wingo-ui.com/blog/category/components)
- Reading time: 9 min
- Canonical: https://wingo-ui.com/blog/framer-motion-spring-presets

## TL;DR

A good framer motion spring set is small: snappy (stiffness 500, damping 32) for presses and toggles, smooth (300 / 30) as the default for dialogs, toasts and layout, bouncy (400 / 18) for rare playful accents, and gentle (120 / 20) for large surfaces and charts. What decides the feel is the damping ratio, damping / (2 * sqrt(stiffness * mass)): under 1 it overshoots, at 1 or more it does not. Put the four in one file, import them everywhere instead of typing numbers inline, and swap them for an instant or fade transition when the user asks for reduced motion.

A framer motion spring is two numbers that decide how every press, toggle and popover in your app feels, and most codebases end up with dozens of them typed inline: `stiffness: 400, damping: 17` here, `260 / 20` there, copied from whatever tutorial was open that day. This tutorial replaces that with four named presets, shows the real stiffness and damping values we ship in Wingo UI's [motion presets](https://wingo-ui.com/components/motion), measures what each one does, and explains which UI gets which spring and how to share them across a whole component library.

The package is called `motion` now. The [motion.dev upgrade guide](https://motion.dev/docs/react-upgrade-guide) (checked October 2026) swaps `framer-motion` for `motion` and moves imports to `motion/react`; the spring options are the same, so a framer motion spring and a motion react spring transition take the same `stiffness`, `damping` and `mass`. Everything below uses motion 12.43.

## How does a Framer Motion spring animation work?

A spring has no fixed duration. Motion simulates a mass on a spring: `stiffness` is how hard the spring pulls toward the target, `damping` is the friction that slows it down, and `mass` is how heavy the thing is (1 by default). The animation ends when the value is close enough to the target and slow enough to stop.

Two consequences matter for UI work:

1. **Feel comes from the ratio, not the raw numbers.** The damping ratio is `damping / (2 * Math.sqrt(stiffness * mass))`. Below 1 the value overshoots and swings back. At 1 or above it arrives without overshoot. Two springs with the same ratio feel like the same curve played at different speeds.
2. **A spring keeps its speed when you retarget it.** Motion's [transition docs](https://motion.dev/docs/react-transitions) say physics based springs incorporate the velocity of existing gestures or animations, and duration based ones do not. Tap a segmented control twice quickly and the thumb bends toward the new option instead of stopping and restarting.

You can check the ratio in one line:

```ts
type SpringOptions = { stiffness: number; damping: number; mass?: number };

// under 1 overshoots, 1 or more settles without overshoot
export function dampingRatio({ stiffness, damping, mass = 1 }: SpringOptions) {
  return damping / (2 * Math.sqrt(stiffness * mass));
}

dampingRatio({ stiffness: 300, damping: 30 }); // 0.87
dampingRatio({ stiffness: 400, damping: 18 }); // 0.45
```

## Which stiffness and damping values should a UI use?

If you came for stiffness damping values to copy, these four, from `lib/motion.ts` in Wingo UI, are the only springs our components use:

```ts
import type { Transition } from "motion/react";

export const springs = {
  // press feedback, toggles, switch thumbs, checkmarks: anything that answers a finger
  snappy: { type: "spring", stiffness: 500, damping: 32 },
  // the default: popovers, menus, tab indicators, layout shifts, dialogs
  smooth: { type: "spring", stiffness: 300, damping: 30 },
  // playful accents only: a like, a badge popping in, a success tick
  bouncy: { type: "spring", stiffness: 400, damping: 18 },
  // large surfaces and slow reveals: sheets settling, page sections, chart bars
  gentle: { type: "spring", stiffness: 120, damping: 20 },
} as const satisfies Record<string, Transition>;

export type SpringName = keyof typeof springs;
```

We measured each one with motion's own `spring()` generator from motion-dom 12.43, animating a value from 0 to 100 with a mass of 1. "Within 1%" is the time after which the value stays within one unit of the target.

| Preset | Stiffness / damping | Damping ratio | Overshoot | Within 1% | Use it for |
|---|---|---|---|---|---|
| snappy | 500 / 32 | 0.72 | 4.0% | 296ms | presses, switches, segmented control thumbs, checkmarks |
| smooth | 300 / 30 | 0.87 | 0.4% | 270ms | dialogs, toasts, list rows, tab indicators, layout |
| bouncy | 400 / 18 | 0.45 | 20.5% | 439ms | a like, a badge, a success icon |
| gentle | 120 / 20 | 0.91 | under 0.1% | 486ms | chart bars, page sections, large reveals |
| motion physics default | 100 / 10 | 0.50 | 16.3% | 879ms | what you get from `type: "spring"` alone |

Read the table from the user's side, and note that settle time is not perceived speed. Snappy covers 90% of the distance in 120ms against 188ms for smooth, then spends the rest of its time on a 4% overshoot, which reads as the control reacting to the finger. Smooth settles slightly sooner overall with no visible overshoot, so a dialog opening never looks like it wobbles. Bouncy overshoots by a fifth, which is fun once on a success tick and tiring on every popover. Gentle takes about half a second and stays calm, which suits a whole chart growing in.

The last row is the trap. Write `transition={{ type: "spring" }}` with no numbers and motion falls back to stiffness 100 and damping 10: almost a second of settling and a 16% overshoot. Always pass a named preset.

The demo below doubles as a framer motion spring visualizer: four balls run the four springs side by side. Pick a spring in the playground controls, then replay the list to see it drive real rows.

> Live demo (Motion presets): Watch the four balls race: snappy and smooth land together, bouncy overshoots, gentle arrives last. Then replay the list with each spring. Try it at [Motion presets](https://wingo-ui.com/components/motion) and install it with `npx wingo-ui@latest add motion`.

## What does each spring preset look like in real components?

### Snappy for anything a finger touches

Every pressable surface in the library shares one press: scale to 0.97 on `springs.snappy`. The free [Button](https://wingo-ui.com/components/button) spreads it from a single constant, and so can your own components:

```tsx
"use client";

import { motion } from "motion/react";
import { press } from "@/lib/motion";

// press is { whileTap: { scale: 0.97 }, transition: springs.snappy }
export function LikeButton({ onLike }: { onLike: () => void }) {
  return (
    <motion.button
      type="button"
      onClick={onLike}
      {...press}
      className="h-10 touch-manipulation rounded-full bg-foreground/5 px-4 text-sm font-medium [-webkit-tap-highlight-color:transparent]"
    >
      Like
    </motion.button>
  );
}
```

The free [Switch](https://wingo-ui.com/components/switch) moves its thumb with `animate(pos, checked ? 1 : 0, springs.snappy)` on a motion value, and stretches the thumb on the same spring while it is held. The free [Segmented Control](https://wingo-ui.com/components/segmented-control) slides its thumb on snappy too, and passes the current velocity of the thumb into the next animation, so a second tap mid-slide changes direction without a jolt:

```ts
// the segmented control thumb pattern: retarget without losing speed
animate(x, 0, { ...springs.snappy, velocity: x.getVelocity() });
```

> Live demo (Segmented Control): Tap Day, Week, Month and Year quickly, or drag the thumb: it keeps its speed when you change your mind mid-slide. Try it at [Segmented Control](https://wingo-ui.com/components/segmented-control) and install it with `npx wingo-ui@latest add segmented-control`.

The [React segmented control guide](https://wingo-ui.com/blog/react-segmented-control) covers the rest of that control, from the draggable thumb to radio semantics.

### Smooth as the default for overlays and layout

Dialogs, toasts, list rows and content swaps all enter on `springs.smooth`. Its 0.87 ratio overshoots 0.4%, which on a dialog rising 8px into place is a few hundredths of a pixel, so the surface lands softly without a visible wobble. Popovers and menus are the exception: their preset uses snappy, because they open in direct answer to a click. It is also the right spring for `layout` animations, where a bounce would make neighboring content shake.

```tsx
"use client";

import { AnimatePresence, motion } from "motion/react";
import { springs } from "@/lib/motion";

export function SavedNotice({ show }: { show: boolean }) {
  return (
    <AnimatePresence>
      {show && (
        <motion.p
          initial={{ opacity: 0, y: 4 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, transition: { duration: 0.15 } }}
          transition={springs.smooth}
          className="text-sm text-muted-foreground"
        >
          Changes saved
        </motion.p>
      )}
    </AnimatePresence>
  );
}
```

Notice the exit: a short tween, not a spring. A quick exit reads as responsive and the entrance can take its time, so most presets in `lib/motion` leave in 150ms.

### Bouncy for one accent per screen

Bouncy belongs where a little celebration is the point: the icon tile of an alert dialog pops in on `{ ...springs.bouncy, delay: 0.06 }`, and a success check can do the same. Keep it off anything people open many times a day. The 20% overshoot that delights once becomes noise on the tenth menu.

### Gentle for large surfaces

Big things should move slowly. Chart bars grow on `springs.gentle` with a small per-bar delay, and page sections reveal on it. At stiffness 120 the motion lasts about half a second and never overshoots visibly, so a 300px bar does not bounce past its value and misreport the data for a moment.

## How do you share springs across a component library?

Put them in one file and import them everywhere. Three habits keep that file the only source of spring numbers:

1. **Named presets, never inline numbers.** In Wingo UI, `springs.snappy` and `springs.smooth` each appear in more than 100 component files as of October 2026. About 15 component files still tune a spring inline for a specific gesture, and those are the exceptions worth reviewing, not the rule. Retuning the feel of the whole library is a one line change.
2. **Presets on top of springs.** A preset bundles `initial`, `animate`, `exit` and `transition`, so a popover is `<motion.div {...presets.popover} />` instead of four props that drift apart over time.
3. **One reduced motion path.** `useMotionTransition(springs.smooth)` returns the spring, or `{ duration: 0 }` when the user asked for reduced motion, and `useMotionPreset("popover")` turns movement into a short opacity fade. Both read the preference after hydration, so server and client markup match. Our [prefers-reduced-motion guide](https://wingo-ui.com/blog/prefers-reduced-motion-react) explains why that matters in Next.js.

```tsx
"use client";

import { motion } from "motion/react";
import { springs, useMotionTransition } from "@/lib/motion";

export function TabIndicator({ left, width }: { left: number; width: number }) {
  const transition = useMotionTransition(springs.smooth);
  return (
    <motion.span
      aria-hidden
      initial={false}
      animate={{ x: left, scaleX: width }}
      transition={transition}
      className="absolute bottom-0 left-0 h-0.5 w-px origin-left bg-foreground"
    />
  );
}
```

If you prefer a provider, motion's `MotionConfig` accepts a `transition` that becomes the default for every motion component inside it, so `<MotionConfig transition={springs.smooth}>` at the root replaces motion's built-in defaults for your app. Two caveats: that default also applies to opacity and colors, which then lose motion's 0.3 second tween, and reduced motion is a separate setting: `reducedMotion="user"` on the same `MotionConfig` switches transform animations off when the system asks for it. Components that need snappy still pass it explicitly.

To get the whole file, run `npx wingo-ui@latest add motion`. It is free, installs the `motion` package and the SSR-safe media query hook it uses for reduced motion.

## Can CSS transitions use the same spring?

Yes, for the cases that do not go through motion, such as a Tailwind hover or a `data-state` change. CSS `linear()` takes a list of points and draws the curve through them, so you can sample a spring once and use it as an easing. MDN marks `linear()` as Baseline, available across browsers since December 2023.

Wingo UI ships two sampled twins as tokens in `globals.css`: `--motion-ease-snappy` with `--motion-duration-snappy` (450ms) for snappy, and `--motion-ease-spring` with `--motion-duration-spring` (500ms) for smooth. You can see the overshoot in the numbers: the snappy curve peaks at 1.0398, the 4% from the table above.

```tsx
<div className="transition-transform duration-(--motion-duration-snappy) ease-(--motion-ease-snappy) data-[open=true]:scale-100 data-[open=false]:scale-95" />
```

A sampled curve cannot keep velocity when it is interrupted, so use it for simple state changes and keep drags, thumbs and anything retargeted often on motion springs.

## What mistakes make springs feel wrong?

- **Animating with springs that were never named.** Every inline `stiffness` is a future inconsistency. Search your codebase for `stiffness:` outside the presets file.
- **Bouncy on things people open all day.** Keep the overshoot for moments that deserve it.
- **Bouncy springs on opacity and colors.** Opacity stops at 1, so a 20% overshoot is invisible and only delays the end of the animation. A fade on its own reads better as a short tween, which is also motion's default for non-transform values. When opacity rides along with a transform on smooth, as in the presets above, the 0.4% overshoot is too small to matter.
- **Duration and physics in one object.** Motion's docs state that `bounce` and `duration` are overridden when `stiffness`, `damping` or `mass` are set, so `{ duration: 0.2, stiffness: 400 }` silently ignores the duration.
- **No reduced motion path.** Movement that ignores the system setting can make people sick. Route every spring through one hook.

For more guides like this one, browse [component guides](https://wingo-ui.com/blog/category/components), or start with the pillar, our guide to [React form components](https://wingo-ui.com/blog/react-form-components-guide), which uses these springs in every field.

## What should you install next?

Start with the presets: `npx wingo-ui@latest add motion` copies `lib/motion.ts` into your project, free, with the four springs, the presets, the gesture thresholds and the reduced motion hooks. Add the free Button, Switch or Segmented Control next and they pick up the same springs automatically.

## Components in this post

- [Motion presets](https://wingo-ui.com/components/motion) (Free): The shared springs, enter and exit presets, stagger helpers and reduced-motion hooks every animated component uses. Install: `npx wingo-ui@latest add motion`
- [Button](https://wingo-ui.com/components/button) (Free): The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps. Install: `npx wingo-ui@latest add button`
- [Switch](https://wingo-ui.com/components/switch) (Free): A React toggle switch for settings that apply at once: a spring thumb that stretches and drags, thumb icons, and async saves that roll back. Install: `npx wingo-ui@latest add switch`
- [Segmented Control](https://wingo-ui.com/components/segmented-control) (Free): A React iOS-style segmented control with a raised thumb that slides and follows your finger. Install: `npx wingo-ui@latest add segmented-control`

## FAQ

### What are good stiffness and damping values for a Framer Motion spring?

For UI, stiffness between 120 and 500 with a damping ratio between about 0.7 and 0.9 covers most needs; go lower only for a deliberate bounce. Our four presets are 500 / 32 for presses and toggles, 300 / 30 for overlays and layout, 400 / 18 for playful accents and 120 / 20 for large surfaces, all with the default mass of 1.

### What spring does Framer Motion use by default?

In motion 12.43 a transform like x or y animates on stiffness 500 and damping 25, scale animates on stiffness 550 and damping 30 (critically damped when it heads to 0), and opacity or colors get a 0.3 second tween. If you pass type: "spring" without numbers, the physics defaults are stiffness 100, damping 10 and mass 1, which overshoots about 16%.

### Should I use duration and bounce or stiffness and damping?

Use duration and bounce when a designer gives you a time, and stiffness and damping when you want the spring to keep the velocity of a gesture or an interrupted animation. Motion's docs say physics springs incorporate that velocity and duration based springs do not, and setting stiffness, damping or mass overrides duration and bounce.

### How do I stop a Framer Motion spring from bouncing?

Raise damping until the damping ratio reaches 1, which means damping at least 2 * sqrt(stiffness * mass). For stiffness 300 and mass 1 that is about 35; anything above it settles without overshoot, just more slowly.

### Is Framer Motion the same as the motion package?

Yes. Framer Motion is now published as motion, and the upgrade guide on motion.dev swaps npm install framer-motion for npm install motion and imports from motion/react. Spring options such as stiffness, damping and mass work the same way.

---

Source: https://wingo-ui.com/blog/framer-motion-spring-presets
