How to Use @starting-style for CSS-Only Entry Animations

The @starting-style CSS at-rule defines an element's initial visual state before the first paint, allowing the browser to animate from that starting state to the final state without JavaScript.

The emilkowalski/skills repository promotes @starting-style as the preferred method for static entry animations in modern web applications. According to the animation standards documented in skills/review-animations/STANDARDS.md, this approach eliminates the need for useEffect hooks or data-mounted attributes while keeping motion definitions in CSS where they belong.

What Is @starting-style?

@starting-style is a CSS at-rule that establishes the initial property values of an element before the browser renders the first frame. When the page loads, the element is painted using the styles inside the @starting-style block; immediately after the first paint, the normal styles take over and the transition animates to the final state.

As implemented in emilkowalski/skills, this technique is particularly effective for toast notifications, modal dialogs, and list items that need to slide or fade into view. The rule is documented at line 89 of skills/review-animations/STANDARDS.md and referenced throughout the skill guides including skills/review-animations/SKILL.md at line 73.

Benefits of CSS-Only Entry Animations

Using @starting-style for enter animations provides specific architectural advantages over JavaScript-driven approaches:

  • Zero-JS overhead – The browser computes the start state during layout, avoiding extra render passes caused by mounting logic or state synchronization.
  • GPU-friendly performance – When combined with transitions on opacity and transform, the animation runs entirely on the compositor thread, minimizing main-thread work.
  • Instant interruptibility – Because the animation uses CSS transitions rather than keyframe animations, new style rules can immediately override or reverse the motion.
  • Improved maintainability – All entrance logic lives in a single @starting-style block rather than being split between CSS and JavaScript files.

Implementing @starting-style in Components

Basic Toast Entry Animation

The following pattern from skills/review-animations/STANDARDS.md demonstrates a standard bottom-up entrance animation:

/* 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 begins off-screen and invisible. Once the browser paints, the transition automatically animates the element to its final position without requiring JavaScript to trigger the animation.

Browser Fallback Strategy

For browsers that do not support @starting-style (older versions of Safari and Chrome), the repository recommends the classic data-mounted pattern documented at line 99 of skills/review-animations/STANDARDS.md:

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

This fallback achieves the same visual effect by adding a data attribute after the first render, though it requires a small amount of JavaScript.

Respecting Reduced Motion Preferences

To maintain accessibility, wrap the animation logic in a prefers-reduced-motion media query. This disables the transition while ensuring the element appears instantly in its final state:

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

Staggered List Entry Animations

For cascading entrance effects, apply @starting-style to list items with custom delay variables:

.list-item {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms ease, transform 300ms ease;
  transition-delay: var(--delay); /* set per item */

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

Assign incremental --delay values (e.g., 0ms, 50ms, 100ms) to each item to create a staggered animation sequence entirely in CSS.

Key Source Files

The implementation patterns above are derived from specific files in the emilkowalski/skills repository:

Summary

  • @starting-style defines initial property values before the first paint, enabling CSS-only entry animations.
  • Zero JavaScript is required for the animation to trigger, reducing boilerplate and improving performance.
  • GPU acceleration is achieved by animating only transform and opacity properties.
  • Graceful degradation is available via the data-mounted attribute pattern for unsupported browsers.
  • Accessibility is maintained by wrapping animations in prefers-reduced-motion media queries.

Frequently Asked Questions

What browsers support @starting-style?

@starting-style is supported in modern versions of Chrome and Edge, with Safari support added in recent releases. For older browsers, the emilkowalski/skills repository recommends implementing a fallback using the data-mounted attribute pattern as documented in STANDARDS.md at line 99.

How does @starting-style differ from CSS keyframe animations?

Unlike @keyframes, which define explicit animation steps, @starting-style establishes the initial state for a CSS transition. The browser automatically interpolates between the starting styles and the element's normal styles, making it ideal for entrance animations that should respond dynamically to style changes.

Can I use @starting-style with React or other frameworks?

Yes, @starting-style works with any framework or vanilla HTML because it is a pure CSS feature. You do not need useEffect, useLayoutEffect, or componentDidMount to trigger the animation, though you may still need JavaScript for the fallback pattern in unsupported browsers.

How do I prevent entry animations for users who prefer reduced motion?

Wrap the @starting-style rule and transition properties in a @media (prefers-reduced-motion: reduce) query. Set the starting styles to match the final state and remove the transition, ensuring the element appears instantly without motion while maintaining layout stability.

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 →