How to Transition Display with CSS allow-discrete
Historically, hiding an element by setting its CSS
display to none caused an immediate
disappearance, instantly cutting off any active exit transitions like
fades or slides. The declaration
transition: display 0.3s allow-discrete solves this issue
by enabling transitions on discrete properties and delaying the switch
to display: none until the specified transition duration
has finished. This article explains how the allow-discrete
keyword alters browser rendering rules, prevents premature removal from
the document flow, and enables seamless exit animations.
The Problem with Discrete Properties
In CSS, properties fall into two categories: interpolable and
discrete. Interpolable properties, such as opacity,
height, and transform, have continuous numeric
values that a browser can smoothly calculate frame-by-frame between
states.
Discrete properties, such as display,
visibility, or content, do not have
intermediate values. An element cannot be "half block and half none." By
default, the browser executes discrete property changes instantly at
\(t = 0\) of an animation frame. When
display: none is applied alongside an opacity fade-out:
- The browser immediately applies
display: none. - The element is instantly stripped from the layout and render tree.
- The accompanying
opacitytransition never renders because non-displayed elements are not painted.
How
allow-discrete Changes the Rendering Engine
The allow-discrete keyword is a value of the
transition-behavior property (included here via the
transition shorthand). It explicitly instructs the
browser's transition engine to allow discrete property types to
participate in transitions.
When allow-discrete is applied, the browser modifies the
timeline of how the property flips between its initial and final
values:
- When transitioning away from a visible display value to
none(Exit): The browser holds the element at its visible state (e.g.,block,flex,grid) for the entire duration (e.g.,0.3s). The property only swaps todisplay: noneat the very end of the transition timeline (\(t = 1.0\)). - When transitioning from
display: noneto a visible value (Entry): The browser swaps the property immediately at the start (\(t = 0.0\)) to make the element renderable, allowing concurrent animations (often paired with@starting-style) to be visible immediately.
Preventing Abrupt Element Removal
By deferring the execution of display: none to the end
of the specified time period,
transition: display 0.3s allow-discrete creates a grace
period. During this window, the element remains part of the render
tree.
This enables other transitions running over the same duration to complete without being interrupted:
.card {
opacity: 1;
transform: scale(1);
display: block;
transition:
opacity 0.3s ease,
transform 0.3s ease,
display 0.3s allow-discrete;
}
.card.is-hidden {
opacity: 0;
transform: scale(0.95);
display: none;
}In this scenario, when .is-hidden is added:
opacitybegins interpolating from1to0over 300ms.transformbegins scaling from1to0.95over 300ms.- Because of
allow-discrete,displayremainsblockfor the entire 300ms duration. - Once the 300ms timer elapses and the visual fade completes, the
browser switches the property to
display: none, safely removing the element from layout calculation, screen readers, and tab order without any visible snap.