HomeJournalThis post

CSS @starting-style for Entry Animations

Build interruption-safe entry transitions for inserted cards, popovers, and dialogs with visible fallback, focus, and reduced-motion behavior.

JP
JP Casabianca
AI Engineer and Product Designer · full-stack delivery · Bogotá

CSS @starting-style gives newly rendered and top-layer elements a defined starting point, allowing an entry transition without a JavaScript staging class. This tutorial treats visibility, interruption, focus, unsupported browsers, and reduced motion as parts of the animation contract rather than polish added later.

CSS @starting-style supplies a before-change style

An element inserted with opacity: 1 has no prior rendered opacity from which an ordinary transition can begin. CSS @starting-style supplies the style used when no previous before-change style exists, such as opacity: 0 and a small translation. The browser can then transition toward the normal computed style. This removes a staging class, but it does not remove the need for authored state and lifecycle decisions.

Keep the base rule useful without animation: the card, popover, or dialog is visible, laid out, and operable. Put only enhancement values in the starting rule. Entry animation CSS should never rely on the starting state to reveal essential content, because an unsupported browser or disabled transition will skip directly to the base style. Visibility is the invariant; motion is one explanation of change.

Inspect the component with transitions slowed in developer tools. A tenfold duration makes overlapping transforms, clipped outlines, and late focus movement obvious without changing the semantic-state implementation.

Entry passes from absent to starting and computed styleA new top-layer card enters with opacity and transform starting values, reaches its computed style, and can reverse safely when interrupted.absentstartingcomputedinterrupt
  • Absent: not rendered or not in top layer
  • Starting: first transition origin
  • Computed: visible settled style
  • Interrupt: reverse from the current visual value
Figure 1: The starting style creates a transition origin without becoming the visible fallback.

Write the smallest inserted-element recipe

Start with a class whose ordinary rule declares opacity, transform, and transition. Nest or separately declare CSS @starting-style with only the initial opacity and transform. Prefer transforms and opacity for composited motion, but verify text rendering and avoid large travel. Keep duration and easing in tokens so motion remains consistent across components and can be shortened globally.

Read the CSS Transitions Level 2 definition for the before-change model and MDN's CSS @starting-style reference for syntax and compatibility notes. Pin the browsers used for screenshots. Support changes quickly, so the tutorial's durable claim is progressive enhancement, not a permanent global availability percentage.

Use logical properties for directional offsets when writing systems can vary across languages. A physical leftward entrance may communicate the wrong spatial relationship in right-to-left layouts and localized navigation flows. Test the longest supported translation at narrow width and zoom before approving the entry animation CSS.

Keep display and the top layer explicit

Popovers and dialogs add two discrete changes: their display state and membership in the top layer. Use transition-behavior: allow-discrete where the supported transition requires a discrete property to switch at the correct point, and include the relevant overlay or display behavior only after testing the exact element lifecycle. Discrete transitions do not interpolate a midpoint; they coordinate when a binary state changes relative to other transitioning properties.

The web.dev entry animation guide demonstrates current patterns for top-layer UI. Treat those patterns as version-sensitive and preserve a visible fallback. CSS @starting-style helps entry, while exit often needs the open-state selector, discrete transition behavior, and careful removal timing. Do not force all three concerns into one clever selector if separate rules make review safer.

Test nested top-layer elements such as a popover launched from a dialog. Their focus, dismissal, and overlay lifecycles need explicit ownership even when each isolated entry transition looks correct.

StateRenderedTop layerFocusMotion
ClosedNo/hiddenNoTriggerNone
EnteringYesYesDialogOptional
OpenYesYesInsideNone
ExitingYesLeavingManagedOptional
ReducedYesYesInsideNear-zero
Figure 2: The live state ledger keeps visibility and focus correct across motion modes.

This dependency-free state fixture models rapid open, close, and reopen commands plus reduced motion; it proves that visual interruption never changes the requested semantic state.

Runnable artifact — entry-animation-state.test.mjs

import assert from "node:assert/strict";
const reduce=(state,event)=>{if(event.type==="OPEN")return{semantic:"open",visual:event.reduced?"settled":"entering"};if(event.type==="CLOSE")return{semantic:"closed",visual:event.reduced?"removed":"exiting"};if(event.type==="END")return state.semantic==="open"?{...state,visual:"settled"}:{...state,visual:"removed"};return state};
let s={semantic:"closed",visual:"removed"};s=reduce(s,{type:"OPEN",reduced:false});s=reduce(s,{type:"CLOSE",reduced:false});s=reduce(s,{type:"OPEN",reduced:false});s=reduce(s,{type:"END"});assert.deepEqual(s,{semantic:"open",visual:"settled"});assert.deepEqual(reduce(s,{type:"CLOSE",reduced:true}),{semantic:"closed",visual:"removed"});console.log("PASS: entry animation interruptions preserve semantic state");

Run node entry-animation-state.test.mjs. Expected receipt: PASS: entry animation interruptions preserve semantic state.

Design interruption before easing curves

Open a popover, close it midway, reopen during exit, navigate away, and press Escape at every phase. CSS transitions normally begin from the current visual value, which can produce a graceful reversal, but DOM removal and top-layer changes may still cut the animation off. Define which state owns removal and which event can be trusted, then add a timeout fallback so a missed transition event cannot strand inert UI.

The product state should remain open or closed; “entering” and “exiting” are visual projections, not alternate authorization or data states. Compare this separation with React data-state animation. CSS @starting-style reduces choreography code precisely when application state stays authoritative. It becomes fragile when JavaScript infers business state by reading opacity or listening to every animated property.

Listen for transition completion on the property that controls removal and ignore bubbled child events. A decorative icon transition must not accidentally tell the parent surface that its exit has finished.

Move focus synchronously with semantic state

When a modal dialog opens, focus should move according to the dialog interaction pattern immediately enough for keyboard and assistive-technology users; it should not wait for a decorative fade. When it closes, restore focus to the invoking control or the next sensible target even if exit motion continues visually. Make the leaving surface inert if it remains rendered, and prevent pointer events from outliving semantic closure.

Test keyboard-only operation, screen-reader announcements, zoom, and forced colors with motion enabled and disabled. CSS @starting-style changes presentation, not focus order or accessibility-tree semantics. A top layer animation that looks fluid but allows focus behind the modal is a broken dialog. Preserve a clear focus indicator throughout transform and opacity changes, especially when scale effects could clip outlines.

Keep the state reducer independent from browser events so races are deterministic in tests. DOM events become inputs with IDs and timestamps rather than hidden commands that mutate several flags ad hoc.

  1. 1Request

    Open or close semantic state

  2. 2Place

    Render, top-layer, and focus synchronously

  3. 3Animate

    Use starting or exit styles when supported

  4. 4Settle

    Remove safely or remain visibly open

Figure 3: The state machine treats motion as a projection of open and closed intent.

Respect reduced motion without deleting feedback

Under prefers-reduced-motion: reduce, remove translation and scaling, shorten or eliminate duration, and keep the final visible style. A subtle opacity change may be acceptable for some products, but do not assume every user preference means the same allowed effect. Centralize the policy and test it. The transition should never delay access, focus, or removal merely to wait through a zero-motion timer.

Motion hierarchy still communicates causality for users who want it. View Transitions and motion hierarchy helps distinguish local entry from page-level continuity, while scroll-driven animation storytelling covers user-controlled progress. CSS @starting-style belongs to newly rendered state, not every visual transition. Choose it when a small arrival cue explains where a surface came from.

Place focus outlines outside overflow-clipped animation wrappers when possible. Transforming an inner visual layer can preserve a stable accessible control box and prevent the indicator from shrinking with decorative scale. Inspect it at 200 percent zoom. Record the focused element before and after every interrupted transition in the component receipt.

Build an unsupported-browser and failure matrix

Test with CSS @starting-style unsupported, allow-discrete unsupported, JavaScript disabled where appropriate, styles delayed, motion reduced, and the dialog closed before first paint. In every case, open content must remain visible and closed modal content must not trap focus. Use feature queries only to add behavior; avoid a base rule that leaves opacity at zero until an enhancement selector overrides it.

Capture mobile widths and 200 percent zoom because translations can move controls outside a clipped container. If custom properties drive duration or distance, register and test them carefully using animatable CSS custom properties. Keep the entry demo dependency-free so support failures are attributable to the platform behavior rather than an animation library's scheduler.

Capture high-contrast and print views even if motion does not run there. The settled component remains part of those media, and starting-state declarations must not leak hidden or transformed styles into output. Save both proofs with the fixture.

Ship the motion contract with the component

Document semantic states, starting values, ordinary computed values, discrete properties, focus movement, interruption rules, removal fallback, reduced-motion policy, supported test matrix, and screenshots. CSS @starting-style should leave less hidden JavaScript, but the saved code is not the main benefit. The real gain is a motion origin expressed beside the component style and reviewable through platform semantics.

Revisit the entry animation when browser behavior, component lifecycle, or focus policy changes. Keep the base state attractive without motion and the top-layer surface operable during every interruption. An entry effect succeeds when users understand the change and never have to fight it; the most sophisticated timeline is still subordinate to visibility, control, and calm.

Give content designers a motion-safe preview with realistic long copy. A compact placeholder can hide wrapping, height, and interruption defects that appear only when a translated dialog grows across several lines.

Create a component specimen that renders an inserted card, popover, and modal dialog side by side at narrow mobile width, long translated copy, 200 percent zoom, reduced motion, forced colors, and an unsupported-feature mode. Drive each surface through open, close, rapid reverse, Escape, outside click, and navigation while recording semantic state, focus owner, top-layer membership, computed opacity and transform, and the event that permits removal. A reviewer should be able to pause any frame and verify that visible content is operable, hidden content is not focusable, and no animation event decides product state. Keep this specimen in visual regression review with a slowed-motion control and a no-transition control. It turns a small CSS feature into a complete entry contract: the base UI is always useful, enhancement explains arrival, interruption follows current intent, and accessibility behavior does not wait for decorative time to finish.