CSS transition-behavior: Animate the Display Property

For years, animating elements entering or leaving the Document Object Model (DOM) required cumbersome JavaScript timeouts or non-semantic CSS visibility workarounds because the display property could not be transitioned. The introduction of the CSS transition-behavior property solves this issue by enabling transitions on discrete properties like display and content-visibility. This article explains how transition-behavior works, why it is essential for modern web animation, and how to implement it alongside @starting-style to create seamless entry and exit transitions using pure CSS.

The Problem with Discrete Properties

In CSS, properties traditionally fall into two categories: interpolable and discrete. Interpolable properties, such as opacity or transform, possess continuous intermediate values that browsers can smoothly blend over time. In contrast, discrete properties—such as display: none versus display: block—switch abruptly with no mathematical midpoint.

Historically, applying a transition to an element fading out to display: none caused the element to disappear immediately, cutting off any simultaneous opacity or transform transitions.

How transition-behavior Works

The transition-behavior property, part of the CSS Transitions Level 2 specification, dictates whether transitions should apply to discrete properties. By default, its value is normal, which excludes discrete properties from transition effects.

When set to allow-discrete, the browser alters the timing of state changes:

  • During Exit Animations: The discrete property change is deferred. If an element transitions from display: block to display: none alongside an opacity fade, the browser keeps display: block active until the duration of all running transitions has elapsed, switching to display: none only at the very end.
  • During Entry Animations: The browser applies the visible display state immediately at the start of the transition, allowing the visual properties to transition smoothly into place.

You can set this property individually or via the transition shorthand:

/* Individual property */
transition-behavior: allow-discrete;

/* Shorthand syntax */
transition: display 0.4s allow-discrete, opacity 0.4s;

Implementing Entry and Exit Transitions

To transition an element entering the page from display: none, the browser needs to know the styles the element should transition from at the moment it becomes visible. This is handled using the @starting-style rule.

Here is a practical implementation of an element transitioning into and out of the view:

.card {
  display: block;
  opacity: 1;
  transform: translateY(0);
  transition: 
    opacity 0.3s ease,
    transform 0.3s ease,
    display 0.3s allow-discrete;
}

/* Hidden state */
.card.is-hidden {
  display: none;
  opacity: 0;
  transform: translateY(20px);
}

/* Entry starting styles */
@starting-style {
  .card {
    opacity: 0;
    transform: translateY(20px);
  }
}

In this setup:

  1. When .is-hidden is removed, the element switches to display: block immediately, starts with the values defined in @starting-style (opacity: 0), and transitions to fully visible.
  2. When .is-hidden is added, opacity and transform begin animating immediately, while the change to display: none is held until the 0.3-second transition finishes.

Benefits in Modern Development

The transition-behavior: allow-discrete property simplifies component architecture. It reduces reliance on JavaScript animation libraries and transitionend event listeners for routine UI components like modals, accordions, tooltips, and dialogs. Coupled with native elements like <dialog> and the Popover API, it provides a performant, accessible, and native mechanism for managing animated UI states directly in CSS.