Using @starting-style for CSS-Only Enter Animations: A Zero-JavaScript Approach

The @starting-style CSS at-rule lets you define an element's initial visual state before the first paint, enabling smooth entry animations through pure CSS transitions without JavaScript mounting logic.

The emilkowalski/skills repository documents a robust pattern for creating performant UI entrance effects using @starting-style. This technique, detailed in skills/review-animations/STANDARDS.md, eliminates the need for React useEffect hooks or data-mounted attributes to trigger initial animations, keeping motion definitions in stylesheets where they belong.

What Is @starting-style?

@starting-style is a CSS at-rule that establishes the initial computed values for an element's properties prior to the browser rendering the first frame. When the page loads, the element initially appears with the styles defined inside the @starting-style block; immediately after the first paint, the element transitions to its normal declared styles.

This mechanism allows the browser to interpolate between the starting state and the final state automatically. According to skills/review-animations/STANDARDS.md, this is the preferred method for static entry animations because the browser computes the start state during layout, avoiding extra render passes caused by JavaScript mounting logic.

Key Benefits of CSS-Only Entry Animations

  • Zero-JavaScript overhead: The browser handles the animation lifecycle internally, removing the need for useEffect hooks to set mounting states.
  • GPU optimization: When combined with transform and opacity properties, the animation runs entirely on the compositor thread, minimizing main-thread work.
  • Interruptibility: Because the animation uses CSS transitions rather than keyframes, it can be overridden instantly by new style rules or user interactions.
  • Maintainability: All entrance logic lives within CSS, reducing component boilerplate and centralizing motion definitions.

Implementation Examples from the Source

Basic Toast Entry Animation

This snippet from skills/review-animations/STANDARDS.md demonstrates a toast notification sliding up while fading in:

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

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

The toast starts off-screen and invisible. Once the browser completes the initial paint, the transition properties automatically animate the element to its final position.

Staggered List Items

For cascading entrance effects, apply a custom delay property to each item while maintaining the CSS-only approach:

.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);
  }
}

Each item receives an inline --delay value (e.g., 0ms, 50ms, 100ms) to create a staggered reveal without JavaScript timers.

Respecting Reduced Motion Preferences

Accessibility requires accommodating users who prefer minimal animation. Wrap the transition in a media query to disable motion while preserving the final state:

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

This ensures the element appears instantly in its final position when prefers-reduced-motion is active.

Fallback Strategy for Legacy Browsers

When @starting-style is unavailable (older versions of Safari or Chrome), the repository recommends a graceful degradation pattern documented at line 99 of skills/review-animations/STANDARDS.md. Use the classic data-mounted attribute approach:

/* Fallback from STANDARDS.md */
.toast[data-mounted] {
  opacity: 1;
  transform: translateY(0);
}
// In a React component
useEffect(() => {
  const el = ref.current;
  if (el) el.dataset.mounted = '';
}, []);

The JavaScript sets the attribute after the initial render, triggering the CSS transition in browsers that lack native @starting-style support.

Summary

  • @starting-style defines initial property values before the first paint, enabling CSS-only entry animations.
  • The technique is documented in skills/review-animations/STANDARDS.md and promoted as the standard for static entry effects in the emilkowalski/skills repository.
  • GPU-friendly animations result from transitioning only transform and opacity.
  • A JavaScript fallback using data-mounted attributes supports legacy browsers while the primary implementation requires zero JavaScript.
  • Always wrap animations in prefers-reduced-motion media queries to maintain accessibility.

Frequently Asked Questions

What browsers support @starting-style?

@starting-style is supported in Chrome and Edge versions 117+, Opera 103+, and Safari 17.5+. For older browsers, implement the data-mounted fallback pattern described in skills/review-animations/STANDARDS.md line 99.

How does @starting-style differ from the data-mounted pattern?

@starting-style is declarative CSS that requires no JavaScript execution to set the initial state, reducing render passes and component complexity. The data-mounted pattern relies on useEffect or similar lifecycle hooks to add an attribute after mounting, which can cause layout thrashing if not handled carefully.

Can I use @starting-style for exit animations?

No. @starting-style specifically handles the entry state before the first paint. For exit animations, use the CSS :exit selector with transition properties or the data-mounted removal technique, as exit states require the element to exist in the DOM before animating out.

Is @starting-style suitable for complex keyframe animations?

While technically possible, the repository recommends @starting-style primarily for simple transitions on opacity and transform. Complex entrance sequences with multiple steps should use @keyframes with animation-fill-mode: forwards, whereas @starting-style excels at single-transition entry effects like fades and slides.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →