Using @starting-style for CSS Entry Animations Without JavaScript

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

The @starting-style at-rule lets you create smooth, performant entry animations entirely in CSS. According to the emilkowalski/skills repository (the Instagit skillset), this technique eliminates the need for React useEffect hooks or data-mounted attributes while keeping animations on the GPU compositor thread.

What @starting-style Does and How It Works

@starting-style declares properties that apply only during the first paint cycle. After that frame renders, the element reverts to its normal styles, triggering CSS transitions to animate between the two states.

This architecture provides three key advantages:

  • Zero JavaScript overhead — the browser computes start states during layout, avoiding extra render passes from mounting logic
  • GPU acceleration — when combined with opacity and transform transitions, animations run entirely on the compositor thread
  • Instant reversibility — CSS transitions can be overridden immediately by new style rules, unlike keyframe animations

The technique is documented in [skills/review-animations/STANDARDS.md](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md#L89) and promoted throughout the animation guides as the preferred approach for static entry animations.

Basic Toast Entry Animation Example

Here's the canonical pattern from STANDARDS.md:

/* 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 invisible and off-screen (translateY(100%)). Once painting completes, the transition automatically animates to the final visible position. No state management, no refs, no effects required.

Browser Fallback Strategy

For older Safari and Chrome versions lacking @starting-style support, the repository recommends the classic data-mounted pattern at line 99 of STANDARDS.md:

/* Fallback when @starting-style isn't supported */
.toast[data-mounted] {
  opacity: 1;
  transform: translateY(0);
}
// In your React component
useEffect(() => {
  const el = ref.current;
  if (el) el.dataset.mounted = '';
}, []);

The JavaScript fallback applies the same visual result but requires an extra render pass. Feature detection via @supports (@starting-style {}) lets you serve the optimal path to each browser.

Respecting Reduced Motion Preferences

Accessibility considerations appear throughout the skillset documentation. Wrap @starting-style in a media query to honor user preferences:

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

This disables the transition while ensuring the element renders immediately in its final state—no disorienting animation, no broken layout.

Staggered List Entry Pattern

For cascading entrances, combine @starting-style with CSS custom properties:

.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 (e.g., calc(var(--index) * 50ms)) to create choreographed reveals without JavaScript timers or batch updates.

Performance and Architectural Benefits

Concern @starting-style Approach Traditional JS Approach
Render passes Single layout + paint Multiple React renders
Thread usage Compositor-only with transform/opacity Main thread for state updates
Bundle impact Zero bytes of JS Hooks, refs, and effect logic
Cancellation Instant via new style rules Requires cleanup functions

The [skills/review-animations/SKILL.md](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md#L73) file explicitly recommends CSS transitions over JavaScript for static entry animations precisely because of these characteristics.

Common Use Cases in the Codebase

The emilkowalski/skills repository identifies these patterns as ideal candidates for @starting-style:

  • Toast notifications — slide up from bottom while fading in
  • Modal dialogs — scale from 95% with opacity fade
  • Dropdown menus — expand from origin point
  • List items — staggered entrance on mount

Each case shares the same requirements: a predictable initial state, a single transition target, and no need for dynamic interruption during the animation.

Key Files and References

File Purpose
skills/review-animations/STANDARDS.md Core documentation with implementation examples and fallback strategy
skills/review-animations/SKILL.md High-level guidance on preferring CSS transitions for entry animations
skills/improve-animations/AUDIT.md Checklist entry for "entry without JS" pattern
skills/emil-design-eng/SKILL.md Concrete component example under "Animate enter states with @starting-style"

Summary

  • @starting-style defines pre-paint styles that enable CSS-only entrance animations
  • No JavaScript required — eliminate useEffect, useState, and data-mounted patterns for static entries
  • GPU-optimized when paired with opacity and transform transitions
  • Graceful degradation via data-mounted fallback for unsupported browsers
  • Accessible by default through prefers-reduced-motion media query integration
  • Best for toast notifications, modals, dropdowns, and staggered list items

Frequently Asked Questions

Which browsers support @starting-style?

Chrome 116+ and Safari 17.4+ support @starting-style. For older versions, implement the data-mounted fallback documented in STANDARDS.md line 99. Use @supports (@starting-style {}) for feature detection.

Can I animate height or width with @starting-style?

Technically yes, but the repository discourages it. Animating layout properties forces main-thread recalculation and subverts the performance benefits. Stick to opacity and transform for compositor-thread animation.

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

@keyframes define an animation sequence that plays once or loops. @starting-style sets a single initial state that transitions to normal styles. The latter integrates naturally with CSS transitions and requires no animation duration declaration in the keyframe itself.

What happens if I wrap @starting-style in a media query?

The rule respects query conditions normally. In prefers-reduced-motion: reduce, setting the starting style equal to the final style prevents any visible animation while maintaining the same structural approach.

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 →