# How Damping at Boundaries Creates Gesture Resistance in Drawers

> Learn how boundary damping creates gesture resistance in drawers. Discover how proportional spring damping slows motion, providing a fluid barrier feel for a better user experience.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-08-02

---

**Increasing spring damping proportionally to overshoot distance progressively slows drawer motion, creating a fluid resistance that feels like pushing against a soft physical barrier rather than hitting a hard stop.**

In gesture-driven interfaces, drawer components simulate real-world physics through **spring-based animations**. The `emilkowalski/skills` repository establishes clear architectural patterns for how these springs should behave when users push beyond a drawer's natural limits. This article examines the specific mechanism—boundary damping—that transforms overscroll gestures into tactile, responsive feedback.

## The Physics of Boundary Resistance

A spring animation has three tunable parameters: **mass** (inertia), **stiffness** (pull toward rest), and **damping** (energy dissipation). When a drawer sits at rest, these values maintain equilibrium. The critical insight from the repository's design system is that **damping should not remain constant** during user interaction.

According to [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) at line 456, the implementation rule is explicit: when a drawer is dragged past its natural boundary, the system must increase damping so "the element moves less the farther it is dragged." This creates a **progressive resistance curve**—the deeper the overshoot, the stronger the opposition to further motion.

The mechanism works through two simultaneous effects:

- **Energy dissipation accelerates**: Higher damping causes the spring to lose kinetic energy faster, reducing displacement per unit of gesture input.
- **Visual velocity decays**: The drawer continues to follow the finger but with diminishing returns, communicating boundary proximity without abrupt halt.

## Spring Configuration Standards

The baseline spring definition appears in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) at line 70:

| Parameter | Default Value | Purpose |
|-----------|-------------|---------|
| Mass | 1 | Establishes base inertia |
| Stiffness | 100 | Determines snap strength |
| **Damping** | **10** | Controls oscillation decay |

This `damping: 10` configuration provides a slightly underdamped response—quick settlement with minimal bounce—for standard drawer animations. However, the repository emphasizes that this value is **contextually modifiable**.

The [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) file at line 147 introduces a tension with Apple's recommended approach: Apple prefers **critical damping** (`damping = 1.0`) as the default, only reducing damping (increasing bounce) when gestures carry significant momentum. The repository reconciles this by treating boundary overshoot as a distinct state requiring **elevated** rather than reduced damping.

## Implementing Dynamic Boundary Damping

### Framer Motion Approach

The following implementation derives damping directly from overshoot distance, following the pattern documented in the repository:

```tsx
import { motion, useMotionValue, useTransform } from 'framer-motion';
import { useRef } from 'react';

function Drawer() {
  const y = useMotionValue(0);
  const containerRef = useRef<HTMLDivElement>(null);
  const DRAWER_HEIGHT = 300;

  // Map overshoot to increased damping: base 10 → max 30
  const damping = useTransform(y, (value) => {
    const overshoot = Math.max(0, -value - DRAWER_HEIGHT);
    return 10 + Math.min(overshoot / 10, 20);
  });

  return (
    <motion.div
      ref={containerRef}
      drag="y"
      dragConstraints={{ top: -500, bottom: 0 }}
      dragElastic={0}
      style={{ y }}
      transition={{
        type: 'spring',
        mass: 1,
        stiffness: 100,
        damping,  // MotionValue dynamically bound
      }}
    >
      {/* Drawer content */}
    </motion.div>
  );
}

```

Key implementation details:

- `dragElastic={0}` prevents the default elastic overshoot behavior, allowing manual damping control.
- The `damping` MotionValue recalculates on every frame based on current `y` position.
- Damping scales linearly with overshoot up to a ceiling (30), preventing excessive stiffness.

### Native Web Animations API

For environments without React, the same principle applies using raw pointer events:

```javascript
const drawer = document.getElementById('drawer');
const DRAWER_HEIGHT = 300;
const BASE_DAMPING = 0.05;

let startY = 0;
let currentY = 0;

drawer.addEventListener('pointerdown', (e) => {
  startY = e.clientY;
  drawer.setPointerCapture(e.pointerId);
});

drawer.addEventListener('pointermove', (e) => {
  const delta = e.clientY - startY;
  currentY = Math.min(0, delta);  // Constrain to downward drag
  
  const overshoot = Math.max(0, -currentY - DRAWER_HEIGHT);
  const damping = BASE_DAMPING + Math.min(overshoot / 5000, 0.15);
  
  // Apply damping through cubic-bezier control point
  drawer.animate(
    { transform: `translateY(${currentY}px)` },
    { 
      duration: 0, 
      fill: 'both',
      easing: `cubic-bezier(${damping}, 0, 0, 1)` 
    }
  );
});

drawer.addEventListener('pointerup', () => {
  // Snap back with elevated damping for crisp settlement
  drawer.animate(
    { transform: 'translateY(0)' },
    { duration: 300, easing: 'cubic-bezier(0.2, 0, 0.2, 1)' }
  );
});

```

This implementation approximates spring behavior through **cubic-bezier easing**, where the first control point's x-coordinate functions as a damping proxy. Higher values produce steeper curves, effectively resisting motion.

## Behavioral Outcomes

The boundary damping pattern produces three distinguishable interaction qualities:

1. **Continuous feedback**: Unlike hard stops that break gesture continuity, progressive damping maintains the finger-to-element mapping.
2. **Predictable limits**: Users intuitively perceive the boundary's location through accumulating resistance rather than visual indicators.
3. **Clean release**: Upon gesture termination, the elevated damping causes rapid settlement without oscillation, reinforcing the boundary's physicality.

The repository's guidance in [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) emphasizes that this approach aligns with **spatial interface design**—interfaces that establish persistent physical metaphors aid user navigation and reduce cognitive load.

## Performance Considerations

Dynamic damping requires per-frame recalculation, but the operations involved (subtraction, `Math.max`, division) are computationally trivial. Both implementations above maintain 60fps on modern devices. For constrained environments:

- **Pre-compute damping curves**: Generate lookup tables for common overshoot ranges (0-100px, 100-200px, etc.).
- **Throttle motion value updates**: In Framer Motion, use `useSpring` with custom `damping` rather than `useTransform` for optimized interpolation.

## Summary

- **Damping at boundaries** creates gesture resistance by proportionally increasing energy dissipation as overshoot distance grows.
- The `emilkowalski/skills` repository establishes `damping: 10` as the baseline spring value in [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md), with contextual elevation for overscroll states.
- **Progressive resistance** replaces hard stops, maintaining gesture continuity while communicating physical limits.
- Implementations in Framer Motion and native APIs both derive damping from `overshoot = currentPosition - boundaryPosition`.
- Upon release, elevated damping ensures rapid, non-oscillating settlement to the rest position.

## Frequently Asked Questions

### Why increase damping instead of stiffness at boundaries?

**Stiffness pulls the spring toward its rest position with greater force, creating a "rubber band" effect that accelerates return velocity.** Elevated damping, by contrast, resists motion in both directions without adding restorative energy. This produces passive resistance—the drawer feels heavy at the edge rather than actively snapping back. The [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) guidance explicitly favors damping for this reason: it "makes the element move less" rather than "pull it back."

### What happens if damping is too high at boundaries?

**Excessive damping (>50 in Framer Motion's scale) creates a "mud" effect where the drawer barely responds to gestures.** Users experience this as broken or laggy input. The repository recommends capping elevation—`Math.min(overshoot / 10, 20)` in the example above—to preserve responsiveness. Critical damping (no oscillation) should be the target maximum, not exceeded significantly.

### How does this differ from UIScrollView's built-in bounce behavior?

**UIKit's default `bounces` property uses elasticity (effectively reduced stiffness beyond bounds) rather than increased damping.** The repository's approach inverts this: instead of making the boundary "stretchy," it makes the system "heavier." Both communicate limits, but damping-based resistance feels more substantial—like compressing foam versus pulling a spring. The [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) notes that Apple's own apps vary by context, with sheets favoring the damping pattern.

### Can boundary damping be applied to horizontal drawers or modals?

**The principle generalizes to any axis-constrained gesture.** Replace the `y` MotionValue with `x` for side drawers, or apply radial constraints for circular menus. The core calculation—`overshoot = current - limit` followed by `damping = base + f(overshoot)`—remains identical. The repository's [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md) suggests this pattern applies universally to "any element with spatial boundaries."