# CSS Standard Animation on Modals: A Production-Ready Approach

> Implement production-ready CSS modal animations using opacity and transform transitions. Ensure smooth, accessible experiences with custom easing and prefers-reduced-motion support.

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

---

**Use CSS transitions on `opacity` and `transform` with custom easing tokens and `prefers-reduced-motion` gating for smooth, accessible modal animations that run off the main thread.**

The **Skills** repository by Emil Kowalski provides a design-first framework for building UI motion through an opinionated, skill-driven system. For developers implementing **CSS standard animation on modals**, the repository offers battle-tested recipes that prioritize performance, accessibility, and visual consistency across components.

## How the Animate Skill Structures Modal Animations

Modal animations in the Skills framework live within the **animate** skill, located at [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md). This file establishes a strict decision-making process for all UI motion: whether to animate, the purpose behind the animation, tool selection, property choices, easing definitions, duration constraints, interruption handling, and reduced-motion gating.

For standard modals, the skill mandates **CSS transitions** over keyframe animations. Transitions run on the compositor thread, eliminating main-thread jank during heavy page loads. The recommended properties—`opacity` and `transform`—are the only two CSS properties guaranteed to avoid layout recalculation or paint operations.

## The Modal Animation Recipe

The implementation details reside in [`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md), which contains copy-ready CSS for common components. Here is the exact modal pattern defined in that file:

```css
/* Modal component */
.modal {
  /* Modals are centered, so we use a neutral transform-origin */
  transform-origin: center; 
  transition:
    opacity 250ms var(--ease-out),
    transform 250ms var(--ease-out);
}

/* Starting / ending states supplied by the skill */
.modal[data-starting-style],
.modal[data-ending-style] {
  opacity: 0;
  transform: scale(0.96);
}

/* Backdrop fades in sync with the modal */
.backdrop {
  transition: opacity 250ms var(--ease-out);
}

/* Reduced-motion fallback */
@media (prefers-reduced-motion: reduce) {
  .modal,
  .backdrop {
    transition: opacity 100ms ease;
    transform: none;
  }
}

```

### Design Tokens and Easing

The custom property `--ease-out` is defined in [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md) alongside other motion tokens like `--ease-in-out` and `--ease-drawer`. These tokens ensure identical easing curves across all animated components, maintaining a cohesive visual language without developers manually copying cubic-bezier values.

The `--ease-out` curve translates to `cubic-bezier(0.23, 1, 0.32, 1)`—an ease-out-quint that delivers snappy, natural deceleration.

## Accessibility and Device Adaptation

The Skills framework enforces **reduced-motion gating** at the skill level. Beyond the `prefers-reduced-motion` media query shown above, the system also considers input modality. As documented in the skill files, touch-only devices should skip transform animations that provide no functional benefit:

```css
@media (hover: hover) and (pointer: fine) {
  .element:hover { transform: scale(1.05); }
}

```

When `prefers-reduced-motion: reduce` is active, the modal transitions to opacity-only with a shortened 100ms duration and zero transform overhead. This preserves contextual awareness without triggering vestibular discomfort.

## JavaScript Fallback with Web Animations API

For cases where declarative CSS transitions are insufficient—such as dynamically injected modals or complex sequencing—[`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md) provides a Web Animations API equivalent:

```js
element.animate(
  [
    { opacity: 0, transform: 'scale(0.96)' },
    { opacity: 1, transform: 'scale(1)' }
  ],
  {
    duration: 250,
    easing: 'cubic-bezier(0.23, 1, 0.32, 1)', /* matches --ease-out */
    fill: 'forwards'
  }
);

```

This JavaScript approach mirrors the CSS token values exactly, ensuring identical perceived motion regardless of implementation path.

## How the Skills System Enforces Quality

The repository's architecture connects multiple skills into a verification chain:

- **[`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md)** — Defines the process, tokens, and hard rules for all animation work
- **[`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md)** — Provides validated code patterns like the modal above
- **[`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md)** — Enforces strict style and performance guidelines that modal implementations must satisfy
- **[`skills/pick-ui-library/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/pick-ui-library/SKILL.md)** — Directs agents toward pre-built components (like toast systems) when hand-rolled animation is inappropriate

By adhering to the **CSS standard animation on modals** pattern from [`RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/RECIPES.md), developers automatically satisfy the review rules without additional validation steps.

## Performance Characteristics

The modal animation recipe achieves optimal rendering performance through specific technical choices:

- **Compositor-only properties** — `opacity` and `transform` never trigger layout or paint
- **250ms duration** — Fast enough to feel responsive, slow enough to perceive directionality
- **Ease-out easing** — Natural deceleration that feels physically plausible
- **Scale(0.96) start state** — Subtle enough to avoid jarring size jumps, noticeable enough to communicate emergence

These constraints emerge from the skill's decision tables, which weigh motion purpose against visual budget and cognitive load.

## Summary

- **CSS transitions on `opacity` and `transform`** provide the foundation for standard modal animations in the Skills framework
- **Custom easing tokens** (`--ease-out`, etc.) defined in [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md) guarantee consistency across components
- **`[data-starting-style]` and `[data-ending-style]` attributes** manage enter/exit states without JavaScript animation libraries
- **`prefers-reduced-motion` and input media queries** gate motion appropriately for accessibility and device context
- **[`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md)** contains production-ready code that satisfies [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md) requirements automatically

## Frequently Asked Questions

### What makes this modal pattern "standard" in the Skills framework?

The pattern is standard because it follows the **animate skill's** mandatory decision sequence: it uses CSS transitions (not keyframes), targets compositor-only properties, applies token-defined easing, and includes reduced-motion fallbacks. Any deviation requires justification against the review rules in [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md).

### Why scale to 0.96 instead of 0.9 or smaller?

The 0.96 scale value represents a **minimal meaningful difference**—large enough to signal emergence, small enough to avoid distraction. According to the skill's visual budget principles, modals should feel present without demanding attention. The 4% reduction matches similar patterns for drawers and dialogs throughout [`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md).

### Can I use this pattern with React or other frameworks?

Yes. The CSS pattern is framework-agnostic. The `[data-starting-style]` and `[data-ending-style]` attributes can be toggled by any framework's state management. For React specifically, you might apply these attributes during `useLayoutEffect` or use the Web Animations API fallback for more complex orchestration.

### Where does the cubic-bezier value come from?

The `cubic-bezier(0.23, 1, 0.32, 1)` value is the concrete definition of the `--ease-out` token specified in [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md). This curve provides a dramatic ease-out that feels snappier than standard CSS `ease-out` while remaining physically believable.