# CSS Drawer Animation with Custom Easing: A Complete Implementation Guide

> Implement smooth iOS style drawer animations using CSS custom properties and transform transitions. Explore custom easing for enhanced user experience in web apps.

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

---

**Use a CSS custom property like `--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1)` combined with GPU-accelerated `transform` transitions to create smooth, iOS-style drawer animations in web applications.**

The **emilkowalski/skills** repository provides a battle-tested pattern for implementing drawer animations with custom easing curves. This living styleguide codifies UI motion language through shared animation tokens, standardized recipes, and documentation-driven code reviews. Here's how to implement the drawer animation pattern using the actual source files and conventions from the repository.

## Why Custom Easing Matters for Drawer Animations

Native-feeling drawer animations rely on **cubic-bezier curves** that accelerate quickly then settle softly. The repository's `--ease-drawer` token replicates the iOS-style motion found in Apple's own interfaces, providing that characteristic "snappy-then-smooth" feel that users associate with quality interactions.

Without a centralized easing vocabulary, teams inevitably drift toward inconsistent timing—some drawers feel sluggish, others too abrupt. The **emilkowalski/skills** architecture solves this by defining curves once and consuming them everywhere.

## The `--ease-drawer` Token Definition

The custom easing curve lives in **[`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md)** at line 34:

```css
:root {
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); /* iOS-like drawer curve (Ionic) */
}

```

This **cubic-bezier(0.32, 0.72, 0, 1)** curve produces:
- **Fast initial acceleration** (0.32, 0.72) — the drawer snaps into motion immediately
- **Long deceleration tail** (0, 1) — the drawer settles gently into its final position

The repository centralizes this definition so that any component referencing `--ease-drawer` automatically receives design-system-approved motion.

## The Drawer Recipe from RECIPES.md

The ready-to-use implementation appears in **[`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md)** (lines 110-120):

```css
.drawer {
  /* slide in/out using transform */
  transition: transform 500ms var(--ease-drawer);
}

/* Closed state – move the drawer completely out of view */
.drawer[data-closed] {
  transform: translateY(100%); /* 100% = height of the drawer itself */
}

```

Three architectural decisions make this recipe performant and maintainable:

| Decision | Rationale |
|----------|-----------|
| **`transform` property** | GPU-accelerated, bypasses main-thread layout and paint |
| **`translateY(100%)`** | Percentage is relative to the element's **own height**, making the recipe height-agnostic |
| **`500ms` duration** | Falls within the "occasional" animation range (200-500ms) defined in the standards table |

The **[`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md)** file provides additional context for why percentage-based translate beats fixed pixel values—your drawer works whether it's 200px or 600px tall without code changes.

## Complete Working Example

Here's a full implementation combining the token, recipe, and interactive state management:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>CSS Drawer Animation with Custom Easing</title>
  <style>
    :root {
      /* From skills/review-animations/STANDARDS.md */
      --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
    }

    body {
      margin: 0;
      font-family: system-ui, sans-serif;
    }

    .drawer {
      position: fixed;
      inset: 0 auto 0 0;          /* left-hand drawer */
      width: 300px;
      height: 100%;
      background: #fff;
      box-shadow: 2px 0 8px rgba(0,0,0,.15);
      
      /* RECIPES.md pattern: closed by default */
      transform: translateY(100%);
      transition: transform 500ms var(--ease-drawer);
    }

    /* Open state – match the [data-closed="false"] convention */
    .drawer[data-closed="false"] {
      transform: translateY(0);
    }
  </style>
</head>
<body>
  <button id="toggle">Toggle Drawer</button>

  <aside class="drawer" data-closed="true" id="myDrawer">
    <h2>Menu</h2>
    <nav>
      <a href="#">Home</a>
      <a href="#">Settings</a>
      <a href="#">Profile</a>
    </nav>
  </aside>

  <script>
    const btn = document.getElementById('toggle');
    const drawer = document.getElementById('myDrawer');
    
    btn.addEventListener('click', () => {
      const isClosed = drawer.getAttribute('data-closed') === 'true';
      drawer.setAttribute('data-closed', String(!isClosed));
    });
  </script>
</body>
</html>

```

This follows the **RECIPES.md** convention of using `data-closed` attributes—boolean strings rather than presence/absence—making the state inspectable and explicit.

## Token Reference in SKILL.md

For a complete list of available easing tokens, **[`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md)** at line 109 documents the full vocabulary:

```css
:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.87, 0, 0.13, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  /* ...additional tokens */
}

```

Each token carries **semantic meaning**: `--ease-drawer` is reserved specifically for sheet/drawer presentations, while `--ease-out` handles generic exit animations. This naming convention prevents misuse—don't reach for `--ease-drawer` on a tooltip fade.

## Ensuring Compliance Through Review Workflows

The **emilkowalski/skills** repository enforces drawer animation standards through:

1. **[`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md)** — the master table defining allowed durations and required tokens
2. **[`AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/AUDIT.md)** — checklist for verifying existing implementations
3. **[`FIND-ANIMATION-OPPORTUNITIES.md`](https://github.com/emilkowalski/skills/blob/main/FIND-ANIMATION-OPPORTUNITIES.md)** — guidance for identifying mismatched curves

During code review, search for `--ease-drawer` usage with a simple grep:

```bash
grep -r "\--ease-drawer" src/

```

Any drawer implementation missing this token flags a standards violation.

## Adapting for Horizontal Drawers

The recipe uses `translateY(100%)` for bottom-sheet style drawers. For left/right drawers, swap to `translateX`:

```css
.drawer-left {
  transform: translateX(-100%);
  transition: transform 500ms var(--ease-drawer);
}

.drawer-left[data-closed="false"] {
  transform: translateX(0);
}

```

The **same easing curve** applies regardless of direction—the curve reflects the *interaction pattern* (sheet presentation), not the axis of motion.

## Summary

- Define **custom easing once** as a CSS custom property in your design tokens — the **`emilkowalski/skills`** repository uses `--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1)` for iOS-style sheet animations
- Reference the token in **component recipes** using GPU-accelerated `transform` transitions with percentage-based translations
- Keep **duration at 500ms** for "occasional" interactions per the standards table in [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md)
- Use **`data-closed` attributes** for explicit state management and easier debugging
- Verify implementations through **repository grep tools** during code review to enforce consistency

## Frequently Asked Questions

### What makes the `--ease-drawer` cubic-bezier curve feel "iOS-like"?

The **cubic-bezier(0.32, 0.72, 0, 1)** curve has a steep initial slope (fast acceleration) and a very long tail (extended deceleration). This mimics how physical sheets behave: they start moving quickly when pulled, then seem to "settle" into place with a soft landing. The values derive from Ionic Framework's drawer implementation, which itself studied Apple's native motion curves.

### Why use `translateY(100%)` instead of `translateY(-300px)`?

**Percentage values scale with the element's own dimensions**, so your animation works regardless of drawer height. As documented in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md), this future-proofs your CSS against design changes—a 200px drawer and a 600px drawer both slide completely out of view with identical code. Fixed pixel values break if the height changes.

### How do I audit existing drawer implementations for standards compliance?

Run `grep -rn "\--ease-drawer"` across your codebase to find usages. Any drawer component missing this token—or using hardcoded cubic-bezier values—violates the **`emilkowalski/skills`** standards. Also verify that animations use `transform` only (no `top`, `left`, `height`, or `margin` transitions) and fall within the 200-500ms duration window defined in [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md).

### Can I reuse `--ease-drawer` for other animation patterns?

**Avoid it.** The repository explicitly scopes `--ease-drawer` to sheet/drawer presentations. For tooltips or modals, use `--ease-out` or `--ease-in-out` as defined in [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md). Mismatching curves to interaction types creates subtle "uncanny valley" effects where animations feel technically correct but emotionally wrong.