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

@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.

/* 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 (line 89) and 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:

/* Fallback for unsupported browsers */
.toast[data-mounted] {
  opacity: 1;
  transform: translateY(0);
}
// 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

/* 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

.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

@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 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:

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—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.

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 →