5 min read

Using CSS transitions and animations, you can create animation effects even with display: none.

Table of Contents

Description of the Current State

An element is hidden using display: none, and when it’s made visible via JavaScript, you might expect it to have a fade-in effect with an opacity transition. However, the transition property doesn’t work in this case.

Here’s an example:

<div id="element" class="element">placeholder</div>
.element {
  display: none;
  opacity: 0;
  transition: .2s;
}
.element.active {
  display: block;
  opacity: 1;
}
element.classList.add('active');

At this stage, the element appears abruptly rather than fading in with an opacity transition.

To address this, two common methods have been used in the past:

  1. Make the element visible with an opacity of 0, then change its opacity to 1 in the next render cycle.
  2. Use a CSS animation. Refer to the following code:
.element.active {
  display: block;
  animation: fadeIn .2s both;
}
@keyframes {
  from { opacity: 0; }
  to { opacity: 1; }
}

However, the two methods above are a bit inconvenient compared to simply using a transition property.

So, is there a simpler way to add animation when transitioning an element from display: none to display: block?

Yes, there is! Modern browsers have recently introduced a new CSS property called transition-behavior, which can meet this need.


Introduce to transition-behavior

The syntax is as follows:

transition-behavior: allow-discrete;
transition-behavior: normal;

Among them:

  • allow-discrete allows discrete CSS properties to support transition effects, with the display property being the most representative example.
  • normal refers to the previous transition behavior, where, except for visibility (which I have always considered a discrete CSS property), other discrete properties did not support transition effects.

Usage example: You can use the transition property to achieve a transition effect between display:inline ↔ none.

Here is the HTML:

<button id="trigger">show or hide image</button>
<img id="target" src="https://raw.githubusercontent.com/trevortylerlee/n1/main/n1.jpeg" />

Using the toggle of the hidden attribute to achieve visibility changes:

trigger.onclick = function () {
    target.toggleAttribute('hidden');	
};

OK, now for the most important CSS—it’s really simple, and can be done with just a few lines of code:

img {
    transition: .25s allow-discrete;
    opacity: 1;
}

img[hidden] {
    opacity: 0;
}

effect1

Awesome!

Of course, the CSS code above can also be written separately:

transition-duration: .25s;
transition-behavior: allow-discrete;

However, when clicking the button again, expecting it to fade out, there is no fade-out effect.

Problem Analysis and Solution

Why does setting transition-behavior: allow-discrete allow the display: none transition to be visible after the transition duration ends, making the opacity transition visible to the naked eye?

However, the transition from display: none to display: block appears suddenly. This is because, in the browser’s rendering process, the change from display: none to display: block and the opacity change to 1 happen in the same render frame. Since there’s no initial opacity, you don’t see the animation effect.

So, is there a way to make the display change with a transition effect?


Using the @starting-style Rule to Declare the Transition Starting Point

As the name suggests, @starting-style is used to declare the starting style, specifically for transition effects.

For example, to achieve a fade-in effect when the element is displayed, it’s simple—just add three more lines of code:

img {
    transition: .25s allow-discrete;
    opacity: 1;
    @starting-style {
      opacity: 0;
    }
}

Alternatively, you can write it like this without using CSS nesting syntax:

img {
  transition: .25s allow-discrete;
  opacity: 1;
}
@starting-style {
  img {
    opacity: 0;
  }
}

At this point, when we click the button to display the image, we can see it fade in with a transparency animation, as shown below.

CSS is becoming more powerful.

This article introduces two key concepts: first, transition-behavior: allow-discrete, which allows elements with display: none to have a fade-out effect; and second, the @starting-style rule, which enables elements to transition from display: none with a fade-in effect.

In practical web development, popovers (floating layers) and dialog boxes are often toggled based on the display property.

With the introduction of transition-behavior and @starting-style, the interactivity of these native HTML elements has been significantly enhanced, addressing long-standing limitations. It’s truly exciting—CSS is definitely becoming more powerful.

To stay updated on the latest CSS properties and their applications, be sure to follow me