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
useEffecthooks to set mounting states. - GPU optimization: When combined with
transformandopacityproperties, 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-styledefines initial property values before the first paint, enabling CSS-only entry animations.- The technique is documented in
skills/review-animations/STANDARDS.mdand promoted as the standard for static entry effects in theemilkowalski/skillsrepository. - GPU-friendly animations result from transitioning only
transformandopacity. - A JavaScript fallback using
data-mountedattributes supports legacy browsers while the primary implementation requires zero JavaScript. - Always wrap animations in
prefers-reduced-motionmedia 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →