1. Prompt and context
A dialog is hidden with display: none. Product wants it to fade in and out, leaving layout only after the animation. The team transitions only opacity, so entry has no visible animation and exit disappears immediately. Explain discrete properties, design the CSS, and cover browser fallback, focus management, and prefers-reduced-motion.
2. What the interviewer is testing
- Whether you know that discrete properties such as
displayandcontent-visibilityhave no continuous intermediate values. - Whether you can use
transition-behavior: allow-discreteor the shorthand correctly instead of treating it as a duration. - Whether you understand that the flip timing differs for entry and exit, and that
@starting-styleestablishes an initial style. - Whether you include compatibility, accessibility, focus, reduced motion, and real rendering tests.
3. Clarifying questions before answering
- Is this a normal element, a native dialog, or a top-layer popover?
- Must it leave layout and become unfocusable, or is skipping paint sufficient?
- Which browser versions are supported, and what is the fallback without discrete transitions?
- How should focus, Escape, backdrop clicks, and reduced-motion preferences behave during the animation?
4. A 30-second answer framework
display is discrete, so it does not interpolate by default. Use transition-behavior: allow-discrete to opt it into a transition and also provide a non-zero duration for opacity or transform. On entry, switch to a visible value early enough for the whole animation; on exit, keep the element visible until the end. Use @starting-style for a first inserted frame. Older browsers can ignore the enhancement while keeping the continuous fallback; focus, hidden semantics, and reduced motion still need explicit handling.
5. Step-by-step deep answer
Step 1: Separate continuous and discrete properties
opacity and transform can produce continuous intermediate values; display only switches values. Without allow-discrete, the browser changes display immediately, so a closing element is removed before its opacity transition can be seen and an opening element has no visible start frame. transition-behavior only permits the discrete transition; transition-property and a non-zero transition-duration are still required.
Step 2: Build a bidirectional panel transition
Declare the continuous fallback first, then an enhanced declaration with allow-discrete. During exit, display should flip near 100% so the content remains visible; during entry it should flip to a visible value early. Do not declare only transition-behavior and forget the target opacity or transform state.
.panel {
opacity: 0;
transform: translateY(-0.5rem);
display: none;
transition: opacity 180ms ease, transform 180ms ease;
transition: opacity 180ms ease, transform 180ms ease, display 180ms allow-discrete;
}
.panel[data-open="true"] {
opacity: 1;
transform: translateY(0);
display: block;
}Step 3: Handle first entry and popovers
When an element is inserted dynamically or a popover opens for the first time, there may be no previous visible style to compare. @starting-style supplies the initial opacity and transform. A native popover also enters the top layer, so display and overlay need to be included in the discrete transition or the exit element is removed immediately. Test the combination on the target browser versions.
Step 4: Design the fallback and feature detection
Older browsers ignore the unknown allow-discrete value but should still keep the base opacity/transform transition. Put enhancement rules behind @supports (transition-behavior: allow-discrete) if needed, rather than hiding the entire base style. The fallback must still open, close, focus, and expose correct hidden semantics.
Step 5: Accessibility, performance, and verification
Animation is not the accessibility state. When closed, synchronize hidden, aria-hidden, or native dialog state and return focus to the trigger; when opened, move focus inside and support Escape. Reduce or remove duration under prefers-reduced-motion: reduce. Test layout, paint, keyboard focus, screen readers, rapid toggles, interrupted transitions, and the layout/compositing cost on lower-end devices.
6. Example of a high-quality answer
displayis discrete and has no continuous intermediate value like opacity. I would keep a base opacity/transform transition, then opt display into the enhanced path withtransition-behavior: allow-discrete; entry flips to visible early, while exit stays visible until the end.@starting-stylesupplies the first frame, and popovers also need top-layer display and overlay handling. Older browsers ignore the enhancement but retain the base animation. I would also synchronize focus, Escape, hidden semantics, and reduced motion, then verify the behavior across browsers and keyboard flows.
7. Common mistakes
- Transitioning only opacity → display changes immediately → add allow-discrete while retaining a continuous property.
- Treating
transition-behavioras a duration → the discrete property still does not transition → declare the property and a non-zero duration too. - Flipping display to none at the start of exit → the fade cannot be seen → align the discrete flip with the end of the exit duration.
- Ignoring first insertion → the first open has no entry animation → define an initial frame with
@starting-style. - Testing only mouse and modern browsers → keyboard, screen-reader, or fallback states fail → include supports fallback, focus, and reduced-motion checks.
8. Follow-ups and responses
Why should display switch at the end of exit?
If it switches to none immediately, the element leaves layout and rendering, so there is no visible content for the rest of the opacity duration. Keep it visible near 100%, then enter the hidden state.
Does allow-discrete interpolate display every frame?
No. It permits a discrete value to participate in a transition, but the value still flips at a defined point. Continuous visual change comes from opacity, transform, or another interpolable property.
How do you keep older browsers usable?
Declare the base transition without allow-discrete first, then enhance with @supports or a later declaration. If enhancement is unavailable, accept an instantaneous display switch while preserving button behavior, focus, and hidden semantics.
How do you handle reduced motion?
Under prefers-reduced-motion: reduce, shorten the duration substantially or set it to zero while keeping state synchronization, focus movement, and close controls. Do not merely hide the animation and leave an invisible focusable element.