# Using @starting-style for CSS-Only Element Entry Animations: A Complete Guide

> Learn how to create CSS-only element entry animations with @starting-style. Define initial states for smooth, JavaScript-free entrance effects. Get the complete guide.

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

---

**`@starting-style` is a CSS at-rule that defines an element's initial visual state before the first paint, enabling smooth entrance animations without JavaScript.**

The `@starting-style` rule lets you declare starting values for properties that differ from the element's default styles. Once the browser renders the first frame, normal styles apply and CSS transitions animate the change to the final state. This technique—documented extensively in the `emilkowalski/skills` repository—eliminates the need for `useEffect` hooks, `data-mounted` attributes, or any JavaScript timing logic for static entry animations.

## How @starting-style Works

The `@starting-style` block lives inside a CSS rule and contains property values that exist only for that crucial first frame.

```css
/* From skills/review-animations/STANDARDS.md */
.toast {
  /* Final state */
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;

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

```

The element renders at `opacity: 0` with `translateY(100%)`, then immediately transitions to the visible, centered position. The browser handles the timing automatically.

## Why Prefer @starting-style Over JavaScript

According to [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (line 89) and [`SKILL.md`](https://github.com/emilkowalski/skills/blob/main/SKILL.md) (line 73), this approach offers three concrete advantages:

- **Zero-JS overhead** – The browser computes start states during layout, avoiding extra render passes from mounting logic
- **Compositor-thread execution** – Animations on `opacity` and `transform` run on the GPU, keeping the main thread free
- **Instant interruptibility** – CSS transitions can be overridden immediately by new style rules, unlike animation libraries that queue state changes

## Browser Fallback Strategy

When `@starting-style` isn't supported—older Safari and Chrome versions—the repository recommends the classic `data-mounted` pattern documented at line 99 of [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md):

```css
/* Fallback for unsupported browsers */
.toast[data-mounted] {
  opacity: 1;
  transform: translateY(0);
}

```

```javascript
// Minimal React bridge for the fallback
useEffect(() => {
  const el = ref.current;
  if (el) el.dataset.mounted = '';
}, []);

```

The JavaScript runs only when necessary. Feature-detect `@starting-style` support and conditionally apply the fallback.

## Practical Implementation Patterns

### Toast Notification Entry

```css
/* skills/review-animations/STANDARDS.md */
.toast {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;

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

```

The toast slides up from the bottom while fading in—no pop-in, no layout shift.

### Staggered List Items

```css
.list-item {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms ease, transform 300ms ease;
  transition-delay: var(--delay);

  @starting-style {
    opacity: 0;
    transform: translateY(20px);
  }
}

```

Set `--delay` per item (`0ms`, `50ms`, `100ms`) for cascading entrance effects. All timing lives in CSS.

### Reduced Motion Support

```css
@media (prefers-reduced-motion: reduce) {
  .toast {
    transition: none;
    @starting-style {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

```

Users who prefer reduced motion see the final state instantly without animation.

## Performance Characteristics

The [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) file identifies `@starting-style` as the "entry without JS" pattern for auditing purposes. Key metrics:

| Property | Impact |
|----------|--------|
| `opacity` | Composite-only, minimal cost |
| `transform` | GPU-accelerated, no layout recalculation |
| `@starting-style` overhead | Single frame, discarded after paint |

Avoid animating `width`, `height`, `margin`, or `top`/`left`—these trigger layout and paint, negating the performance benefits.

## File Reference Guide

These source files contain implementation details and architectural decisions:

- **[`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md)** – Core documentation, fallback strategy (line 99), and canonical examples
- **[`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md)** – High-level guidance on preferring CSS transitions over JavaScript
- **[`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md)** – Checklist reference for the entry-without-JS pattern
- **[`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md)** – Concrete component example in the "Animate enter states with @starting-style" section

## Summary

- **`@starting-style`** defines pre-paint property values for CSS-only entrance animations
- **No JavaScript required** for static entry effects—transitions handle the interpolation
- **Fallback to `data-mounted`** when browser support is missing
- **Animate only `opacity` and `transform`** to stay on the compositor thread
- **Wrap in `prefers-reduced-motion`** to respect accessibility preferences

## Frequently Asked Questions

### Which browsers support @starting-style?

Chrome 117+, Edge 117+, and Safari 17.4+ support `@starting-style`. Firefox has not yet implemented the feature. The `emilkowalski/skills` repository recommends feature detection rather than browser sniffing, applying the `data-mounted` fallback only when the at-rule fails to parse.

### Can @starting-style animate height or width?

Technically yes, but the repository explicitly discourages this. Animating dimensional properties triggers layout recalculation and harms performance. Stick to `opacity` and `transform` per the standards in [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md)—these properties can be promoted to GPU layers without main-thread involvement.

### How does @starting-style differ from CSS @keyframes?

`@keyframes` define explicit animation sequences that run for a set duration. `@starting-style` declares a single initial state that immediately transitions to normal styles. Use `@starting-style` for simple entry effects; reserve `@keyframes` for complex multi-stage animations or looping effects.

### What happens if I nest @starting-style inside a media query?

The rule respects its container's conditions. Nesting inside `@media (prefers-reduced-motion: reduce)` is the recommended pattern for accessibility—the starting state matches the final state, eliminating motion while maintaining visual content.