How to Use @starting-style for CSS Enter Animations

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

The emilkowalski/skills repository documents this technique as the preferred method for handling static entry animations. According to skills/review-animations/STANDARDS.md (line 89) and the broader animation guides, using @starting-style for CSS enter animations eliminates the need for useEffect hooks or data-mounted attributes while keeping motion definitions entirely in your stylesheets.

What is @starting-style?

@starting-style is a CSS at-rule that declares the initial appearance of an element before the browser renders the first frame. When the page loads, the element immediately adopts the properties defined inside the @starting-style block. After the first paint, the element's normal styles take over, and any defined transition animates the properties to their final state.

This mechanism makes it possible to create smooth entrance animations—such as fades, slides, or scale effects—using only CSS, without requiring JavaScript to trigger the animation or manage mount states.

Why Use @starting-style for Enter Animations?

The emilkowalski/skills repository promotes @starting-style as the standard for entry animations because of several architectural advantages:

  • Zero-JavaScript overhead – The browser computes the start state during layout, avoiding extra render passes caused by mounting logic.
  • GPU-friendly performance – When combined with transitions on opacity and transform, animations run on the compositor thread, minimizing main-thread work.
  • Graceful degradation – For browsers that don't support @starting-style (older Safari or Chrome versions), the repository provides a fallback pattern using the data-mounted attribute.
  • Interruptibility – Because these are CSS transitions rather than keyframe animations, they can be overridden instantly by new style rules, supporting dynamic UI interactions.

Implementation Examples from emilkowalski/skills

Basic Toast Entry Animation

As documented at line 89 of skills/review-animations/STANDARDS.md, the following pattern demonstrates a standard fade-and-slide entrance:

.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 painted, the transition automatically animates it to the final position without JavaScript intervention.

Fallback for Unsupported Browsers

For environments where @starting-style isn't available, line 99 of skills/review-animations/STANDARDS.md recommends the classic data-mounted pattern:

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

This approach achieves the same visual effect by triggering the transition via attribute selection after the component mounts, as referenced in skills/review-animations/SKILL.md.

Respecting Reduced-Motion Preferences

Accessibility requires honoring user motion preferences. Wrap the animation in a media query to disable transitions while preserving the final state, a practice noted in skills/improve-animations/AUDIT.md:

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

Staggered List Entry

Create cascading entrance effects by combining @starting-style with custom properties, as shown in skills/emil-design-eng/SKILL.md:

.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 a unique --delay value (e.g., 0ms, 50ms, 100ms) to create the staggered effect without JavaScript timers.

Browser Support and Fallback Strategy

While @starting-style is supported in modern Chromium-based browsers, older versions of Safari and Chrome lack support. The emilkowalski/skills repository addresses this through progressive enhancement:

  1. Primary approach: Use @starting-style for capable browsers.
  2. Fallback: Implement the data-mounted attribute pattern for legacy support.
  3. Detection: The JavaScript fallback only runs when the CSS at-rule isn't recognized, ensuring optimal performance in modern browsers.

Performance and Architectural Considerations

Aspect Why it matters How @starting-style helps
Performance Animating only transform and opacity keeps work on the GPU. Defines these properties in the initial state, then transitions them.
Interruptibility Dynamic UI requires the ability to cancel or reverse animations. CSS transitions can be overridden instantly by new style rules.
Maintainability Reduces boilerplate and keeps motion in CSS. All entrance logic lives in the @starting-style block.
Accessibility Reduced-motion users need minimal movement. Can be wrapped in prefers-reduced-motion media queries.

Summary

  • @starting-style defines an element's initial state before the first paint, enabling CSS-only enter animations.
  • Zero-JavaScript required for modern browsers, though the data-mounted fallback handles legacy support as documented in skills/review-animations/STANDARDS.md.
  • GPU-optimized when animating opacity and transform, keeping main-thread work minimal.
  • Accessible by wrapping animations in prefers-reduced-motion media queries.
  • Interruptible transitions support dynamic UI changes better than keyframe animations.

Frequently Asked Questions

What browsers support @starting-style?

Modern Chromium-based browsers support @starting-style, while older versions of Safari and Chrome may not. The emilkowalski/skills repository recommends using the data-mounted attribute fallback for these cases, ensuring animations work everywhere while optimizing for modern implementations.

How is @starting-style different from CSS animations?

@starting-style works with CSS transitions, not @keyframes animations. It defines the starting values of properties before the first paint, allowing the browser to interpolate to the final state naturally. This approach is more interruptible than keyframe animations and requires no JavaScript to trigger, unlike traditional mount-based animation patterns.

Can I use @starting-style with React?

Yes, @starting-style works seamlessly with React and other frameworks. Because the rule handles the initial state automatically, you can remove useEffect hooks that previously managed mount states. For server-side rendering or hydration scenarios, the browser handles the initial paint state without hydration mismatches, as documented in skills/review-animations/SKILL.md.

How do I handle users who prefer reduced motion?

Wrap your @starting-style rules in a @media (prefers-reduced-motion: reduce) query. Set the transition to none and ensure the @starting-style block contains the final state values (opacity: 1, transform: translateY(0)). This ensures the element appears instantly in its final position without animation, respecting user accessibility preferences.

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 →