# How to Animate Enter and Exit States with `@starting-style` in CSS

> Animate enter and exit states with @starting-style in CSS. Define initial visual states before first paint to enable entry animations without JavaScript. Learn how to transition styles smoothly.

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

---

**The `@starting-style` CSS at-rule lets you define an element's initial visual state before the first paint, enabling entry animations without JavaScript by declaring starting properties that automatically transition to the element's normal styles.**

The `emilkowalski/skills` repository documents how to leverage `@starting-style` in CSS to create smooth enter and exit animations while eliminating the need for React mount flags or `useEffect` tricks. This approach reduces JavaScript bundle size and guarantees animations run even when JavaScript is disabled.

## What is `@starting-style`?

`@starting-style` is a modern CSS at-rule that specifies the initial visual state of an element *before* the browser applies its normal styles. When an element renders, the browser instantly swaps the starting style for the final style, triggering any declared CSS transitions to create fluid entry animations.

### How the At-Rule Works

The rule is parsed as a nested block inside a selector. Properties declared within `@starting-style` apply only during the first paint cycle. Immediately after, the browser applies the element's standard computed styles, causing the transition to animate from the starting values to the final values. This mechanism is documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) within the `emilkowalski/skills` repository.

## Creating Entry Animations

To animate an element as it enters the DOM, define both the final state and the starting state within the same selector. The browser automatically handles the transition between these states on mount.

```css
.toast {
  /* Final state after mount */
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;

  /* Initial state – applied only on first paint */
  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

```

In this example from [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md), the toast starts fully transparent and translated downward. On the first paint, it instantly adopts these starting values, then transitions to the final `opacity: 1` and `transform: translateY(0)` states over 400 milliseconds.

## Handling Exit Animations

Since `@starting-style` only manages entry states, exit animations require a different approach. You implement exits using standard CSS transitions by toggling a class that changes the property values toward their initial states.

```css
/* Exit state triggered by adding a class */
.toast.exit {
  opacity: 0;
  transform: translateY(100%);
}

```

When the `exit` class is applied—or when the element is removed from the DOM—the browser transitions the properties back to their initial values, creating a smooth exit animation.

## Browser Support and Fallback Strategy

`@starting-style` is currently supported in Chrome 106+, Edge 106+, and Safari 16.0+. For browsers lacking support, you can fall back to the classic mount-after-render pattern using data attributes.

### The Data-Mounted Fallback

When `@starting-style` is unavailable, manually control the initial state using a `data-mounted` attribute set via JavaScript:

```css
[data-mounted="true"] .toast {
  opacity: 1;
  transform: translateY(0);
}

[data-mounted="false"] .toast {
  opacity: 0;
  transform: translateY(100%);
}

```

In React, implement this pattern using `useEffect` to set the mounted state after the initial render:

```jsx
function Toast({ children }) {
  const [mounted, setMounted] = React.useState(false);
  
  // Only runs where @starting-style isn't supported
  React.useEffect(() => setMounted(true), []);

  return (
    <div className="toast" data-mounted={mounted}>
      {children}
    </div>
  );
}

```

This fallback ensures your animations work across all browsers while allowing you to remove the JavaScript overhead in modern environments.

## Summary

- **`@starting-style`** defines initial CSS property values that apply only during the first paint, enabling pure CSS entry animations without JavaScript mount flags.
- **Entry animations** work by declaring starting properties inside `@starting-style` and letting the browser transition to the element's normal styles.
- **Exit animations** require toggling classes or removing elements to trigger standard CSS transitions, as `@starting-style` only handles the initial render.
- **Browser support** includes Chrome 106+, Edge 106+, and Safari 16.0+, with a `data-mounted` fallback pattern available for unsupported browsers as documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md).

## Frequently Asked Questions

### Does `@starting-style` work for exit animations?

No, `@starting-style` only handles entry animations by defining the initial state before the first paint. For exit animations, you must use standard CSS transitions by adding a class that changes properties toward their final hidden state, or by removing the element from the DOM entirely.

### Which browsers support `@starting-style`?

The `@starting-style` at-rule is supported in Chrome 106 and later, Edge 106 and later, and Safari 16.0 and later. According to the `emilkowalski/skills` repository standards, you should implement a fallback using `data-mounted` attributes for browsers that do not yet support this feature.

### How do I animate elements that are added to the DOM dynamically?

`@starting-style` automatically applies to any element when it first renders, regardless of when it is added to the DOM. Simply include the `@starting-style` block within the element's selector, and the browser applies the starting styles on the next paint cycle, then transitions to the final state.

### Can I use `@starting-style` with `display: none`?

`@starting-style` works best with properties that support transitions, such as `opacity` and `transform`. Since `display` is not animatable, you should combine `@starting-style` with `opacity` for the visual animation, and control the `display` property separately using the `allow-discrete` property or by managing visibility through other means.