---
name: css-keyframes-animations
description: This skill should be used when writing or reviewing CSS animations in Angular components. Use when creating entrance/exit animations, implementing @keyframes instead of @angular/animations, applying timing functions and fill modes, creating staggered animations, or ensuring GPU-accelerated performance. Essential for modern Angular 20+ components using animate.enter/animate.leave directives and converting legacy Angular animations to native CSS.
---

# CSS @keyframes Animations

## Overview

Implement native CSS @keyframes animations for Angular applications, replacing @angular/animations with GPU-accelerated, zero-bundle-size alternatives. This skill provides comprehensive guidance on creating performant entrance/exit animations, staggered effects, and proper timing configurations.

## When to Use This Skill

Apply this skill when:
- **Writing Angular components** with entrance/exit animations
- **Converting @angular/animations** to native CSS @keyframes
- **Implementing animate.enter/animate.leave** in Angular 20+ templates
- **Creating staggered animations** for lists or collections
- **Debugging animation issues** (snap-back, wrong starting positions, choppy playback)
- **Optimizing animation performance** for GPU acceleration
- **Reviewing animation code** for accessibility and best practices

## Quick Start

### Basic Animation Setup

1. **Define @keyframes** in component CSS:
```css
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
```

2. **Apply animation** to element:
```css
.element {
  animation: fadeIn 0.3s ease-out;
}
```

3. **Use with Angular 20+ directives**:
```html
@if (visible()) {
  <div animate.enter="fade-in" animate.leave="fade-out">
    Content
  </div>
}
```

### Common Pitfall: Element Snaps Back

**Problem:** Element returns to original state after animation completes.

**Solution:** Add `forwards` fill mode:
```css
.element {
  animation: fadeOut 1s forwards;
}
```

### Common Pitfall: Animation Conflicts with State Transitions

**Problem:** Entrance animation overrides initial state transforms (e.g., stacked cards appear unstacked then jump).

**Solution:** Animate only properties that don't conflict with state. Use opacity-only animations when transforms are state-driven:
```css
/* BAD: Overrides stacked transform */
@keyframes cardEntrance {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* GOOD: Only animates opacity, allows state transforms to apply */
@keyframes cardEntrance {
  from { opacity: 0; }
  to { opacity: 1; }
}
```

## Core Principles

### 1. GPU-Accelerated Properties Only

**Always use** for animations:
- `transform` (translate, rotate, scale)
- `opacity`

**Avoid animating** (triggers layout recalculation):
- `width`, `height`
- `top`, `left`, `right`, `bottom`
- `margin`, `padding`
- `font-size`

### 2. Fill Modes

| Fill Mode | Behavior | Use Case |
|-----------|----------|----------|
| `forwards` | Keep end state | Exit animations (stay invisible) |
| `backwards` | Apply start state during delay | Entrance with delay (prevent flash) |
| `both` | Both of above | Complex sequences |

### 3. Timing Functions

Choose appropriate easing for animation type:

```css
/* Entrance animations - ease-out (fast start, slow end) */
animation-timing-function: cubic-bezier(0, 0, 0.58, 1);

/* Exit animations - ease-in (slow start, fast end) */
animation-timing-function: cubic-bezier(0.42, 0, 1, 1);

/* Bouncy overshoot effect */
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
```

Tool: [cubic-bezier.com](https://cubic-bezier.com) for custom curves.

### 4. Staggered Animations

Create cascading effects using CSS custom properties:

**Template:**
```html
@for (item of items(); track item.id; let idx = $index) {
  <div [style.--i]="idx" class="stagger-item">
    {{ item.name }}
  </div>
}
```

**CSS:**
```css
.stagger-item {
  animation: fadeSlideIn 0.5s ease-out backwards;
  animation-delay: calc(var(--i, 0) * 100ms);
}
```

### 5. Accessibility

Always respect reduced motion preferences:

```css
@media (prefers-reduced-motion: reduce) {
  .animated {
    animation: none;
    /* Or use simpler, faster animation */
    animation-duration: 0.1s;
  }
}
```

## Common Animation Patterns

### Fade Entrance/Exit

```css
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}

.fade-in { animation: fadeIn 0.3s ease-out; }
.fade-out { animation: fadeOut 0.3s ease-in forwards; }
```

### Slide Entrance

```css
@keyframes slideInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.slide-in-up { animation: slideInUp 0.3s ease-out; }
```

### Scale Entrance

```css
@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

.scale-in { animation: scaleIn 0.2s ease-out; }
```

### Loading Spinner

```css
@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #f3f3f3;
  border-top: 3px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
```

### Shake (Error Feedback)

```css
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
  20%, 40%, 60%, 80% { transform: translateX(5px); }
}

.error-input {
  animation: shake 0.5s ease-in-out;
}
```

## Angular 20+ Integration

### Basic Usage with animate.enter/animate.leave

```typescript
@Component({
  template: `
    @if (show()) {
      <div animate.enter="fade-in" animate.leave="fade-out">
        Content
      </div>
    }
  `,
  styles: [`
    .fade-in { animation: fadeIn 0.3s ease-out; }
    .fade-out { animation: fadeOut 0.3s ease-in forwards; }

    @keyframes fadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }

    @keyframes fadeOut {
      from { opacity: 1; }
      to { opacity: 0; }
    }
  `]
})
```

### Dynamic Animation Classes

```typescript
@Component({
  template: `
    @if (show()) {
      <div [animate.enter]="enterAnim()" [animate.leave]="leaveAnim()">
        Content
      </div>
    }
  `
})
export class DynamicAnimComponent {
  show = signal(false);
  enterAnim = signal('slide-in-up');
  leaveAnim = signal('slide-out-down');
}
```

## Debugging Animations

### Common Issues

| Problem | Cause | Solution |
|---------|-------|----------|
| Animation doesn't run | Missing duration | Add `animation-duration: 0.3s` |
| Element snaps back | No fill mode | Add `animation-fill-mode: forwards` |
| Wrong starting position during delay | No backwards fill | Add `animation-fill-mode: backwards` |
| Choppy animation | Animating layout properties | Use `transform` instead |
| State conflict (jump/snap) | Animation overrides state transforms | Animate only opacity, not transform |

### Browser DevTools

- **Chrome DevTools** → More Tools → Animations panel
- **Firefox DevTools** → Inspector → Animations tab

### Animation Events

```typescript
element.addEventListener('animationend', (e) => {
  console.log('Animation completed:', e.animationName);
  // Clean up, remove element, etc.
});
```

## Resources

### references/keyframes-guide.md

Comprehensive deep-dive covering:
- Complete @keyframes syntax reference
- Detailed timing functions and cubic-bezier curves
- Advanced techniques (direction, play-state, @starting-style)
- Performance optimization strategies
- Extensive common patterns library
- Debugging techniques and troubleshooting

**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details.

### assets/animations.css

Ready-to-use CSS file with common animation patterns:
- Fade animations (in/out)
- Slide animations (up/down/left/right)
- Scale animations (in/out)
- Utility animations (spin, shimmer, shake, breathe, attention-pulse)
- Toast/notification animations
- Accessibility (@media prefers-reduced-motion)

**Usage:** Copy this file to project and import in component styles or global styles:

```css
@import 'path/to/animations.css';
```

Then use classes directly:
```html
<div animate.enter="fade-in" animate.leave="slide-out-down">
```

## Migration from @angular/animations

### Before (Angular Animations)

```typescript
import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  animations: [
    trigger('fadeIn', [
      transition(':enter', [
        style({ opacity: 0 }),
        animate('300ms ease-out', style({ opacity: 1 }))
      ])
    ])
  ]
})
```

### After (CSS @keyframes)

```typescript
@Component({
  template: `
    @if (show()) {
      <div animate.enter="fade-in">Content</div>
    }
  `,
  styles: [`
    .fade-in { animation: fadeIn 0.3s ease-out; }

    @keyframes fadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }
  `]
})
```

**Benefits:**
- Zero JavaScript bundle size (~60KB savings)
- GPU hardware acceleration
- Standard CSS (transferable skills)
- Better performance
