Skip to content
Back to skills

1258 Emil Kowalski 6ad889c1

ASecurity

Emil Kowalski is a Design Engineer at Linear (previously Vercel). Creator of Sonner, Vaul, and the "Animations on the Web" course. His approach emphasizes **restraint, speed, and purposeful motion**. ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
designjavascriptgojavaexpressspringapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1258-emil-kowalski_6ad889c1 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1258 Emil Kowalski 6ad889c1?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1258 Emil Kowalski 6ad889c1
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1258-emil-kowalski-6ad889c1/badge)](https://www.skillsdirectory.com/skills/tools-only-1258-emil-kowalski-6ad889c1)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Emil Kowalski's Animation Principles

Emil Kowalski is a Design Engineer at Linear (previously Vercel). Creator of Sonner, Vaul, and the "Animations on the Web" course. His approach emphasizes **restraint, speed, and purposeful motion**.

---

## Core Philosophy: Restraint & Purpose

Emil's defining contribution to motion design thinking is knowing **when NOT to animate**.

> "The goal is not to animate for animation's sake, it's to build great user interfaces."

### The Frequency Rule

**Animation appropriateness depends on interaction frequency:**

| Frequency | Recommendation |
|-----------|----------------|
| Rare (monthly) | Delightful, morphing animations welcome |
| Occasional (daily) | Subtle, fast animations |
| Frequent (100s/day) | No animation or instant transitions |
| Keyboard-initiated | Never animate |

**The Raycast example**: A tool used constantly throughout the day benefits from zero animation. Users with clear goals "don't expect to be delighted" and prioritize frictionless workflow.

### Speed is Non-Negotiable

> "UI animations should generally stay under 300ms."

A 180ms animation feels more responsive than 400ms. Speed creates perceived performance. When in doubt, go faster.

---

## The 7 Practical Animation Tips

### 1. Scale Your Buttons
Add subtle scale-down on press for immediate feedback:
```css
button:active {
  transform: scale(0.97);
}
```

### 2. Don't Animate from scale(0)
Animating from `scale(0)` creates unnatural motion. Start from `scale(0.9)` or higher for elegant, gentle movement:
```jsx
// BAD
initial={{ scale: 0 }}

// GOOD
initial={{ scale: 0.9, opacity: 0 }}
```

### 3. Tooltip Delay Patterns
First tooltip: delay + animation. Subsequent tooltips in same group: instant (no delay, no animation):
```css
[data-instant] {
  transition-duration: 0ms;
}
```

### 4. Custom Easing is Essential
> "Easing is the most important part of any animation. It can make a bad animation feel great."

Built-in CSS easing (`ease`, `ease-in-out`) lacks strength. Use custom Bézier curves:
- Resources: easing.dev, easings.co
- `ease-out` for entering elements
- `ease-in-out` for moving existing elements
- Springs for interactive elements

### 5. Origin-Aware Animations
Animations should originate from their logical source:
```css
/* Dropdown from button should expand from button, not center */
.dropdown {
  transform-origin: top center; /* or use CSS variables from Radix/Base UI */
}
```

Base UI: `--transform-origin`
Radix UI: `--radix-dropdown-menu-content-transform-origin`

### 6. Keep Animations Fast
- Under 300ms for UI interactions
- 180ms feels more responsive than 400ms
- Remove animations entirely for high-frequency interactions

### 7. Use Blur When Nothing Else Works
Add `filter: blur(2px)` to mask imperfections during state transitions:
```css
.transitioning {
  filter: blur(2px);
}
```
Blur bridges the gap between old and new states, creating smoother perceived motion.

---

## Clip-Path Mastery

Emil advocates for `clip-path` as a powerful animation primitive.

### Why clip-path?
- Hardware-accelerated
- No layout shifts
- No additional DOM elements needed
- Smoother than width/height animations

### Image Reveal Effect
```css
.reveal {
  clip-path: inset(0 0 100% 0);
  animation: reveal 1s forwards cubic-bezier(0.77, 0, 0.175, 1);
}

@keyframes reveal {
  to { clip-path: inset(0 0 0 0); }
}
```

### Tab Transitions with clip-path
Duplicate tab lists with different styling. Animate overlay's clip-path to reveal only active tab—creates smooth color transitions that blend naturally with movement.

### Scroll-Driven with clip-path
```javascript
const clipPathY = useTransform(scrollYProgress, [0, 1], ["100%", "0%"]);
const motionClipPath = useMotionTemplate`inset(0 0 ${clipPathY} 0)`;
```

---

## Spring Physics

Emil uses spring animations extensively with three key parameters:

| Parameter | Effect |
|-----------|--------|
| **Stiffness** | How quickly the spring reaches target (higher = faster) |
| **Damping** | How quickly oscillations settle (higher = less bounce) |
| **Mass** | Weight of the object (higher = more momentum) |

### Spring for Mouse Position
Use `useSpring` for mouse-position-driven components:
```javascript
const springConfig = { stiffness: 300, damping: 30 };
const x = useSpring(mouseX, springConfig);
const y = useSpring(mouseY, springConfig);
```

This interpolates value changes with spring behavior rather than instant updates—nothing in the real world changes instantly.

---

## Interruptibility

Great animations can be interrupted mid-play and respond naturally:

- Framer Motion supports interruption natively
- CSS transitions allow smooth interruption before completion
- Enable mid-animation state changes for responsive feel

Test by clicking rapidly—animations should blend smoothly, not queue up.

---

## Performance Principles

### Hardware Acceleration
- Animate `transform` and `opacity` only (composite rendering)
- Avoid `padding`/`margin` changes (trigger layout, paint, composite)
- CSS and WAAPI animations remain smooth regardless of main thread load

### The 60fps Target
- Minimum 60 frames per second
- Test with DevTools Performance panel
- Watch for dropped frames during scroll or interaction

### Shared Layout Animation Gotcha
Framer Motion's shared layout animations can drop frames when browser is busy. For critical animations, CSS solutions move computation off CPU.

---

## When to Use Each Approach

| Context | Approach |
|---------|----------|
| Keyboard shortcuts | No animation |
| High-frequency tool | Minimal or no animation |
| Daily-use feature | Fast, subtle animation (180-250ms) |
| Onboarding/first-time | Delightful animations welcome |
| Marketing/landing page | Full creative expression |
| Banking/serious UI | Minimal, functional motion |
| Playful brand | Bouncy, elastic easing appropriate |

---

## Implementation Patterns from Sonner & Vaul

Emil's open-source component libraries (Sonner for toasts, Vaul for drawers) reveal his philosophy in actual code.

### CSS Transitions Over Keyframes

Keyframes can't be interrupted mid-animation. When users rapidly trigger actions, elements "jump" rather than smoothly retargeting. Use CSS transitions with state-driven classes:

```jsx
// After mount, set state to trigger transition
useEffect(() => {
  setMounted(true);
}, []);

// CSS handles the actual animation
.toast {
  transform: translateY(100%);
  transition: transform 400ms ease;
}
.toast.mounted {
  transform: translateY(0);
}
```

### Direct Style Updates for Performance

CSS variables cause style recalculation across all child elements. For frequent updates (drag operations), update styles directly on the element:

```javascript
// BAD: CSS variable (expensive recalculation)
element.style.setProperty('--drag-y', `${y}px`);

// GOOD: Direct style (no cascade)
element.style.transform = `translateY(${y}px)`;
```

### Match Native Motion Curves

Vaul uses `cubic-bezier(0.32, 0.72, 0, 1)` derived from Ionic Framework to match iOS sheet animations. Duration: 500ms. Familiarity creates perceived quality.

### Momentum-Based Dismissal

Don't require distance thresholds—use velocity (distance / time):

```javascript
const velocity = dragDistance / elapsedTime;
if (velocity > 0.11) {
  dismiss(); // Fast, short gestures work
}
```

Threshold of `0.11` was found through iteration, not calculation.

### Damping for Natural Motion

> "Things in real life don't suddenly stop, they slow down first."

When dragging past boundaries, reduce movement progressively rather than stopping abruptly.

### Multi-Touch Protection

Ignore additional touches after the first until release. Prevents position jumps that feel broken.

### Pointer Capture for Drag UX

Call `setPointerCapture()` during drags so tracking continues even when pointer leaves the element boundary.

### Invisible Quality Details

- **Document visibility**: Pause timers when tab is inactive
- **Hover gap-filling**: `:after` pseudo-elements bridge spacing between stacked elements
- **Scroll-to-drag timeout**: 100ms delay prevents accidental dismissal from momentum

### Sonner Defaults

| Setting | Value | Rationale |
|---------|-------|-----------|
| Duration | 4000ms | Long enough to read, short enough to not annoy |
| Animation | 400ms ease | Smooth but snappy |
| Position | bottom-right | Convention, out of primary content |
| Dismissible | true | User control by default |

### Vaul Defaults

| Setting | Value | Rationale |
|---------|-------|-----------|
| Duration | 500ms | Match iOS sheet feel |
| Easing | cubic-bezier(0.32, 0.72, 0, 1) | iOS-native curve |
| Modal | true | Focus management, overlay |
| Direction | bottom | Convention for mobile sheets |

---

## The Core Philosophy

> "When a feature functions as you assume it should, you proceed without giving it a second thought, which is our goal."

Every detail—from multi-touch handling to damping to pointer capture—serves **invisible quality**. Users shouldn't notice polished interactions; they should just feel right.

---

## Emil vs. Jakub vs. Jhey

| Aspect | Emil | Jakub | Jhey |
|--------|------|-------|------|
| **Focus** | Restraint & speed | Subtle polish | Playful experimentation |
| **Key question** | "Should this animate?" | "Is this subtle enough?" | "What could this become?" |
| **Signature technique** | Frequency-based decisions | Blur + opacity + translateY | CSS custom properties |
| **Ideal context** | High-frequency tools | Production polish | Learning & exploration |

**Synthesis**: Use Emil's framework to decide IF you should animate. Use Jakub's techniques for HOW to animate in production. Use Jhey's approach for learning and experimentation.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.