# How to Use CSS @starting-style for Element Entry Animations

> Learn how to use CSS @starting-style to create elegant element entry animations purely with CSS. Define initial states for stunning entrance effects without JavaScript.

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

---

**CSS `@starting-style` is an at-rule that defines the initial state of an element before the browser paints the first frame, enabling pure CSS entry animations without JavaScript mount effects.**

The `emilkowalski/skills` repository documents modern CSS patterns for component animations, including the `@starting-style` at-rule for declarative entry effects. This technique eliminates the need for React `useEffect` hooks or `data-mounted` attributes to trigger animations, reducing bundle size and improving rendering performance by keeping animations on the GPU.

## Understanding the @starting-style At-Rule

`@starting-style` allows you to specify styles that apply only during the initial paint of an element. When the element renders, the browser applies these starting styles first, then immediately transitions to the element's normal styles using any declared `transition` properties.

According to the repository's animation standards in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 89-96), this pattern replaces the common JavaScript approach of toggling state after mount. The rule ensures that elements appear smoothly without requiring a render-cycle side-effect.

## Basic Syntax and Implementation

The canonical pattern defined in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (lines 19-33) consists of three components:

- **Final state**: The normal styles the element maintains after animation
- **@starting-style block**: The initial styles applied only for the first paint
- **Transition declaration**: How the browser interpolates between states

```css
.toast {
  /* Final state */
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;
  
  /* Initial state - rendered only on first paint */
  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

```

When the `.toast` element enters the DOM, it starts with `opacity: 0` and `transform: translateY(100%)`, then immediately transitions to `opacity: 1` and `transform: translateY(0)` over 400 milliseconds.

## Practical Examples for UI Components

### Toast Entry Animation

The repository defines this pattern for toast notifications that slide up from the bottom while fading in:

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

  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

```

```html
<!-- index.html -->
<div class="toast">New message arrived!</div>

```

This creates a smooth entry animation without any JavaScript initialization code.

### Tooltip Scale Animation

For subtle UI feedback, apply a scale transformation that makes the tooltip appear to grow slightly:

```css
.tooltip {
  opacity: 1;
  transform: scale(0.97);
  transition: opacity 200ms ease-out, transform 200ms ease-out;
  
  @starting-style {
    opacity: 0;
    transform: scale(0.95);
  }
}

```

```html
<div class="tooltip" role="tooltip">Helpful hint</div>

```

### Staggered List Items

Combine `@starting-style` with `transition-delay` to create cascading entry animations:

```css
.item {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms ease-out, transform 300ms ease-out;
  
  @starting-style {
    opacity: 0;
    transform: translateY(20px);
  }
}

/* Stagger delays */
.item:nth-child(1) { transition-delay: 0ms; }
.item:nth-child(2) { transition-delay: 50ms; }
.item:nth-child(3) { transition-delay: 100ms; }

```

```html
<ul>
  <li class="item">First</li>
  <li class="item">Second</li>
  <li class="item">Third</li>
</ul>

```

## Progressive Enhancement and Fallbacks

### Supporting Older Browsers

For browsers that do not yet support `@starting-style`, the repository recommends keeping the `data-mounted` technique as a progressive enhancement safety net:

```css
.toast {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;
  
  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }

  /* Legacy fallback */
  &[data-mounted] {
    opacity: 1;
    transform: translateY(0);
  }
}

```

```jsx
// toast.jsx
export default function Toast({ children }) {
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  return (
    <div className="toast" data-mounted={mounted}>
      {children}
    </div>
  );
}

```

If the browser supports `@starting-style`, the animation runs immediately. Otherwise, the `data-mounted` attribute triggers the same transition after the React effect fires.

## Performance Benefits of @starting-style

Using `@starting-style` for element entry animations provides several advantages over JavaScript-based approaches:

- **No JavaScript overhead**: Eliminates `useEffect` hooks and state management for mount animations, reducing bundle size
- **GPU-only properties**: Restricting animations to `opacity` and `transform` ensures the browser uses compositor threads without layout or paint recalculations
- **Interruptible transitions**: If the element is removed before the transition completes, the browser stops the animation smoothly without visual jumps
- **Declarative reusability**: The same CSS class applies to any element, regardless of framework or rendering context

## Summary

- **`@starting-style`** defines initial element states that apply only during the first paint, before transitions begin
- The pattern documented in `emilkowalski/skills` uses **opacity and transform** properties to ensure GPU-accelerated, 60fps animations
- **No JavaScript is required** for the entry effect, though `data-mounted` attributes provide fallback support for older browsers
- This technique applies to common UI patterns including **toasts, tooltips, dialogs, and list items**
- Reference implementations appear in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) and [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md)

## Frequently Asked Questions

### What is CSS @starting-style used for?

`@starting-style` defines the initial appearance of an element before it receives its normal styles, enabling entry animations without requiring JavaScript to toggle classes after mount. It is particularly useful for components that appear dynamically, such as modals, dropdowns, and notification toasts.

### Do I need JavaScript to use @starting-style?

No. The primary advantage of `@starting-style` is that it eliminates the need for JavaScript mount logic entirely. However, for browsers lacking support, you can implement the `data-mounted` attribute pattern as a progressive enhancement fallback.

### Which properties work best with @starting-style?

**`opacity`** and **`transform`** are the optimal properties because they trigger GPU acceleration without causing layout or paint operations. Avoid animating `width`, `height`, `top`, `left`, or `margin` properties, as these force the browser to recalculate layout and degrade performance.

### How do I handle browsers that don't support @starting-style?

Implement a fallback using the `data-mounted` attribute technique: initialize the element with hidden styles, then use a `useEffect` hook (in React) or equivalent framework lifecycle method to set `data-mounted="true"` after the initial render. Browsers that support `@starting-style` will ignore the fallback, while older browsers will use the attribute-based transition.