1. Question and context
An admin page has a dialog element and a popover menu. The design requires entry and exit transitions without JavaScript changing styles every frame; older browsers must remain usable, and users who reduce motion must not be forced into strong animation. Explain the boundary of @starting-style, the CSS/script split, fallback behavior, and validation.
2. What the interviewer evaluates
- Knowing that a first style update has no previous computed style for a transition and that
@starting-stylesupplies a starting point. - Distinguishing entry and exit, discrete
display, top-layer elements, and ordinary opacity/transform transitions. - Keeping scripts responsible for state changes while the platform handles focus, Escape, semantics, and motion preference.
- Designing capability checks, cancellation races, reduced motion, and real-browser verification instead of pasting CSS.
3. Questions to clarify first
- Is it a dialog element, popover, or ordinary node? Top-layer and default-focus behavior change the design.
- Must the node remain until an exit animation ends, or become non-interactive immediately? This changes
displayand event boundaries. - Should an older browser show a static result, switch instantly, or match pixels exactly?
- With
prefers-reduced-motion, should duration shrink, movement disappear, or all transitions be removed?
4. A 30-second answer
I would let HTML state and semantics own visibility, focus, and closing, while CSS owns reversible visual transitions. Use @starting-style to provide opacity and transform values before the first visible update; use allow-discrete or a compatibility path for display so the element remains paintable during the transition. Exit needs an explicit final state and DOM lifetime, not only a starting rule. Older browsers switch instantly, reduced-motion users get a shorter or absent animation, and keyboard, screen-reader, and race tests validate the result.
5. Step-by-step answer
Step 1: Define state and semantic boundaries
dialog.showModal(), a popover's open state, and a class toggle are different semantics. Ensure a closed control cannot receive focus, an opened control gets an appropriate focus target, and Escape and cancel events work. Animation reflects state; it must not define accessibility state. Scripts should call the platform API or toggle a state attribute, not simulate visibility with timers.
Step 2: Create an entry starting point
On a first style update the browser may have no previous style, so an ordinary transition has no origin. Put the initial values in @starting-style:
.dialog {
opacity: 1;
transform: scale(1);
transition: opacity 180ms ease, transform 180ms ease;
}
@starting-style {
.dialog[open] {
opacity: 0;
transform: scale(0.96);
}
}The starting rule applies to the first style update, not as a persistent closed state. If a component is inserted again, whether entry runs depends on its lifecycle and DOM operation; do not assume every attribute toggle invokes the starting rule.
Step 3: Handle discrete properties and exit
display cannot interpolate like opacity. Keep the element paintable during the transition and switch it to an unavailable state at a discrete point; supported browsers can use transition-behavior: allow-discrete, with an instant fallback elsewhere. Keep a closing node until the transition completes or let the native component own its lifecycle; do not guess with a fixed setTimeout. Filter transitionend by property and handle cancellation, repeated opens, and unmounts.
Step 4: Account for top layer and interaction
Dialogs and popovers in the top layer change stacking, backdrop, and focus behavior. Define content and ::backdrop transitions separately, then test outside clicks, Escape, scroll locking, and multiple instances. Scripts should keep API calls, cancellation, and business callbacks; @starting-style is not a replacement for a JavaScript state machine.
Step 5: Respect motion preference and verify
Under @media (prefers-reduced-motion: reduce), remove movement, shorten duration, or disable transitions while preserving state feedback. Test first open, reopen, rapid toggles, navigation during close, script failure, old browsers, keyboard, screen readers, backdrop, and focus return. Track cancellations, interrupted transitions, hidden-node residue, and accessibility regressions instead of checking screenshots alone.
6. Model answer
I would start with semantics: native dialog or popover APIs plus a small amount of script own open state, focus, and Escape; CSS only presents that state. A normal transition lacks an old style on the first update, so@starting-stylesupplies opacity and transform origins.displayis discrete, so use supported discrete-transition behavior or switch instantly in older browsers. Keep a closing node until the transition ends instead of guessing with a timer. Define top-layer content and backdrop separately, then test reduced motion, rapid toggles, keyboard, and screen readers.
7. Common mistakes
- Treating the starting rule as the closed state: Later toggles have the wrong state; separate persistent, entry, and final closed values.
- Applying an ordinary opacity transition to
display: The element disappears too early or remains interactive; use discrete behavior and a fallback. - Removing nodes with
setTimeout: User settings, cancellation, or dropped frames desynchronize lifecycle; use a valid transition event or native lifecycle. - Letting animation control focus and semantics: Assistive technology follows visual timing; establish HTML/API state first.
- Ignoring reduced motion: The preference is violated; shorten or remove motion while keeping clear state feedback.
8. Follow-up questions and responses
Follow-up 1: Why is an ordinary transition insufficient for the first fade-in?
Before the first style update there may be no previous computed style to compare, so the browser applies the final value directly. @starting-style supplies that origin; it does not define every later closing state.
Follow-up 2: How can display participate in exit?
Keep the element paintable during the transition and switch display at a discrete point. Use allow-discrete where supported and close instantly elsewhere, while ensuring a closed element cannot receive focus or pointer interaction.
Follow-up 3: What happens during rapid open-close-open changes?
The state can reverse before the previous transition finishes. Cancel stale animation work, read the current state, and let the newest state be the only commit; do not run two timers that independently remove and reopen the node. Test alternating clicks, Escape, and navigation teardown.