Animating from “display: block” to “display: none”

Animating display: block to display: noneAs you might already know, CSS3 transitions and animations allow you to animate a specific set of CSS properties. One of the properties that cannot be animated is the display property.

It would be great if we could do it, but it’s not currently possible and I’m guessing it never will be (e.g. how would you animate to “display: table”?). But there are ways to work around it, and I’ll present one way here.

Why the Need to Animate “display”?

The need to animate the display property comes from wanting to solve the following problem:

  • You want to make an element gradually disappear visually from the page.
  • You don’t want that element to take up space after it has disappeared (i.e., you want the disappearance to cause reflow).
  • You want to use CSS for the animation, not a library.

For this reason, animating opacity to zero is simply not enough because an element with zero opacity still occupies the same space on the page.

Let’s look at how we might attempt to solve this problem, step by step.

Use Opacity and Display

The first thing we might think of doing is using both the opacity property and the display property. Our HTML might look like this:

<div id="box" class="box">

<button>TOGGLE VISIBILITY</button>

And our CSS might look like this:

.box {
  background: goldenrod;
  width: 300px;
  height: 300px;
  margin: 30px auto;
  transition: all 2s linear;
  display: block;

.hidden {
  display: none;
  opacity: 0;

Notice we have display: none and opacity: 0 on our “hidden” class. If we toggle this “hidden” class using jQuery, we might have code that looks like this:

var box = $('#box');

$('button').on('click', function () {



But if we do that, we will not see the transition (defined in our .box declaration block) take effect. Instead we see this (which is slightly different in Firefox vs. Chrome/IE10):

JS Bin on

Click the ‘toggle visibility’ button repeatedly and you’ll see the box disappear and appear suddenly, with no transition.

To fix this, we might try to separate our display property from opacity in our CSS:

.hidden {
  display: none;

.visuallyhidden {
  opacity: 0;

Then we could toggle both classes, one after the other:

var box = $('#box');

$('button').on('click', function () {

Besides the fact that we want those two lines reversed when the box is appearing, this won’t work, as shown here:

JS Bin

What we want is for the element to disappear visually, then be removed from the page after it finishes disappearing visually, in a manner similar to a callback function.

(As a side point here, even if we combined opacity: 0 with visibility: hidden, it would animate just fine but the element would still occupy space on the page after it disappears, so that won’t work either.)

Why Doesn’t This Work?

Before getting to my solution, I’ll just explain why you can’t do this by just changing the classes one after the other. First, if you’re adding classes like in the examples above, even if the transition worked, you’d have to set up a separate section for removing the classes and reverse how that’s done (i.e. if the box starts out hidden you have to first set it to display: block, then change the opacity).

But that’s beside the point, because it doesn’t work anyhow. JavaScript executes one line after the other, but it doesn’t wait for all things associated with one line to be finished before executing the next line. (In other words, if you execute an animation or other asynchronous event on line 1, line 2 will proceed even if the animation isn’t done; this clarifies the previous wording I used, as pointed out by Chris).

What’s happening is that the ‘opacity’ is attempting to animate immediately, and even if for a fraction of a millisecond it does start to animate, you won’t see it because the display: none part will take effect just as quickly.

We can sum up the problem we want to solve as follows:

  • When the element is visible, first animate the opacity, then, when that’s finished, make it display: none.
  • When the element is inivisible, first make it display: block, then (while it’s still visually hidden, but existing on the page), animate the opacity.

A Possible Solution

There are probably other ways to do this, and I’d be glad to hear how you’d solve this problem. But here is my solution.

The CSS is the same (with the two ‘hidden’ classes still separated), but the jQuery will look like this:

var box = $('#box');

$('button').on('click', function () {
  if (box.hasClass('hidden')) {
    setTimeout(function () {
    }, 20);

  } else {
    box.addClass('visuallyhidden');'transitionend', function(e) {




And here it is in action:

JS Bin

Here’s a summary of what the code does when the box is visible:

  • Add the visuallyhidden class, which will animate it until it disappears.
  • At the same time that class is added, a single event handler is added using jQuery’s .one() method, waiting for the transitionend event.
  • The transitionend event fires when the opacity is done animating, and when this occurs the element is set to display: block.

Because we can’t detect a transitionend event on the display property, we have to use a different method for when the box is invisible:

  • First, we remove the hidden class, making it display: block while it’s still visually hidden.
  • While this is occuring, the script has executed a delay using setTimeout(), after which the opacity begins to animate.

Take note that some browsers require a prefix for transitionend. Modernizr.prefixed() will help with this.

The delay is very small (20 milliseconds), but because display: block occurs instantly, we don’t need much of a delay at all, just enough to allow the element to take its full place on the page before animating the opacity.

(Another sidepoint: Inside JS Bin in Chrome, I was able to use a delay of just a hundredth of a millisecond (0.1). But Firefox and IE10 both required at least 20ms. Maybe that’s something to do with JS Bin, I really don’t know.)

How Would You Do It?

As mentioned, there are probably other ways to do this, or maybe there’s some improvement that could be made to my code.

Personally, I feel the use of setTimeout is a bit lame and hacky. But hey, it works, and I don’t think it’s going to cause any problems unless you had tons of similar animations on the page. But that would be another issue altogether.

There are a few possible solutions to this on StackOverflow, but I find most, if not all, of the proposed solutions don’t really solve the problem at hand.

Feedback is welcome.

Update (Nov. 21, 2013): See this post by Christian Heilmann, as it addresses the same basic idea. His solution of CSS animations is nice and clean. He also offers one using transitions that someone else suggested.
Web Tools Weekly

77 Responses

  1. I usually avoid display:none and use instead transform:translate(9999px);

    .box {
    transition: opacity 2s linear;
    opacity: 1;
    .hidden {
    opacity: 0;

    • Yes, but that doesn’t solve the problem of removing the space occupied by the original element. Also, in your example, I don’t think you’d see the animation, because the translate transform would occur instantly, removing it from view.

    • Cody:

      My good sir you are a genius.. glad i scrolled to the comments this saved me a lot of headache. Thumbs up

  2. Kalley Powell:

    What if you did something like the following:

    Using step-start and step-end opens up a few other possibilities.

  3. Jon Christensen:

    How about a delay when the hidden class is added and no delay when removed?

    .box {
    opacity: 1;
    transition: opacity 1s, height 0;
    .hidden {
    opacity: 0;
    transition: opacity 1s, height 0 1s;
    height: 0;
    • Yep, this works fine (maybe needs adding some prefixes)! Transition delay of the ‘display’ property FTW!
      Here it is in action:

      • Ilyas:

        It does not work in Chrome on OS X

      • This works in Firefox, Chrome, and Safari if you animate max-height instead of height.

        div.dropnav { opacity: 0.01; max-height: 0px; transition: opacity 1s, max-height 0s 1s; }
        div.navwrapper:hover div.dropnav { opacity: 1; max-height: 999px; transition: opacity 1s, max-height 0s; }

        Set the visible max-height to be something larger than the expected height.

        Yay! An all-CSS solution!

    • great tips! I like this!!!!!!
      It working prettry find for me !!!!
      buy i make a little bit change

      try it out!

      all ease 300ms,display 0s;

  4. JavaScript executes one line after the other, but it doesn’t wait for one line to be finished before executing the next line.

    This is not true at all. JavaScript statements are not asynchronous, although they can invoke asynchronous behavior in other libraries. You may be referring to jQuery transitions here (which are asynchronous) but as written this statement is false. If JavaScript did not guarantee complete execution of one statement before executing the next, the language would be effectively useless.

    • Ah, you’re right. What I’m trying to say is that if the previous line contains an asynchronous event, then it will not wait for that one to finish (in this case, I’m referring to the result of the class being added/removed, which causes asynchronous events to occur). I’ll correct the wording.

  5. How a bout keyframes?

    Where the 0% would set the display property and the 1% – 99% do the opacity and 100% does the display again. ;-)

  6. Andrei Pham:

    Umm, I believe that what you did isn’t enough. Just like you said, when setting display: none on an element, it doesn’t just fade away. It also disappears and the page acts like the element weren’t there. So theoretically, you should animate the “disappearing” thing too, not just the opacity. And this means that after fading it away and before setting display: none to it, you should set the height to 0px smoothly (or width if content flow is horizontal).

    • I’ve updated my codepen to reflect that, I think that works better indeed.
      Width and height are optional of course, depending on what you want animated.

    • I agree, from a visual standpoint, it’s probably jarring to see the content around flow into a new area so suddenly. So yes, you could animate height or width. That’s just one way to do it. And I think you’d also need to do “overflow: hidden” if you use that, depending on what’s inside the element.

      But if you use height, then you have to have a specified height for the element, which is unlikely. Usually content dictates height. And you can’t animate to “height: auto”, so that’s not always an option. Of course there are workarounds for that too, but again, they always have some drawbacks.

      • To solve this in the past I’ve just animated min-height: property. If you have a rough idea of the general height it would have you can get away with using that to hide it.

  7. If in the end you use so much JS, why would you not go for something like $(‘.box’).fadeToggle() ?

    • Because I want to use CSS animations, which generally the browser can optimize better. But probably in a case like this, using it one or two times wouldn’t matter. The idea is to try to get away from jQuery animations, leaving the animating part to CSS where I believe it is more appropriate.

  8. Hi,

    Nice article Louis.

    It’s a shame that you can’t animate from height:auto to height:0 as usually that is what you want. However, if you need a fluid height then don’t use height but use max-height instead although you can’t actually animate the height you can create a decent fade effect and remove the element from the flow.

    Here’s a codepen:

    It’s not perfect but can be useful in certain situations and needs no JS in modern browsers (using the :checked pseudo class instead).

  9. IMO playing with delays is the way to go. So you can combine opacity and top properties, which means our “fadeable” elements are always printed but they are offscreen.

    If you absolutely need to support ie8 then you’d use opacity + top properties:

    .elem {
      transition: opacity .3s ease 0s, top 0s linear .9s;
    .visible {
      transition: top 0s linear 0s, opacity .3s ease .01s;

    And if you don’t care about oldies, then you can go with opacity + translateY properties. This way is surely the best possible because your element gets uploaded to the GPU (translate property) and so you’ll get the lowest possible repaint cost. It could look like this:

    .elem {
      transform: translateY(-2000px) translateZ(0);
      transition: opacity .3s ease 0s, transform 0s linear .9s;
    .visible { 
      transform: translateY(0px);
      transition: top 0s linear 0s, opacity .3s ease .01s;

    Of course I omitted all the vendor prefixes in these little snippets.
    Grunt (or Brunch) Autoprefixer FTW

    • In both those examples, you’re not really solving the problem I outlined at the beginning. In the case of both “top” and “translate”, the element is still going to take up the same amount of space on the page, so it will not cause reflow.

      Your solution is fine if you want to just fade an element out, and don’t care about reflow. But if you want the original occupied space to be gone, then you have to use another method. For details on what I’m talking about, see this post:

  10. Cristian:

    Playing with paddings and height works pretty fine to emulate the desired display effect:

    • Interesting.:) Adding the large top and bottom padding is a neat trick to make the height animate.

      • Cristian:

        Well you need to add 0 padding and then only the needed padding to the container and it will be animated, because height: auto is not possible to animate right now.

  11. Chris:

    How about scaleX(0.00001) as hidden state, then scaleX(1) as visible state, then couple that with opacity:0, opacity:.999 and transition both. scaleX(0.00001) gets the element out of view, like a better visibility: hidden, but uses hw acceleration.

    • Again, that would work fine in many circumstances, but not in the scenario I’ve presented in this post (which is, admittedly, not that common). As I mentioned to others, and in the article, stuff like transforms and opacity make things invisible but they don’t cause page reflow. In other words, if an element is 200×200 in size, if you shrink it down using scaleX/Y, it will still take up 200×200 pixels in space on the page.

      The only way to do what you’re saying is to add “position: absolute” to the element, which takes it out of the flow. So I wonder if that would work better like that…. might be worth experimenting with.

      • Chris Wigley:

        Ah yes, I was assuming the element would be positioned absolute. I use the scale(0.0001) technique and it works well — for elements that are absolutely positioned.

  12. If you need a reflow, then you should read a property which triggers it (e.g. “offsetWidth”). Here’s an eample:

  13. We could also do this animation without any JQuery or javascript by only css properies.
    Some browser in client computers are javascript disabled for security reasons. So I think
    that would be beneficial if you could also mentioned making animation with css only too.

  14. I worked around the display none/block limitation by using height 0 like the earlier comments above. Needed to play an animation then remove it afterwards:

    .item-to-animate {
        height: 0;
        animation: some-animation
    @keyframes some-animation {
    	0%, 100% {
    		height: auto;
    	0% {
                    /* start animating stuff */
    	99.99999% {
                    /* end animating stuff */
  15. We often use JQuery because it is possible to make pages more user friendly and fun. Thanks for the useful tutorial.


  16. sachin Tendulkar:

    Thanks for the useful tutorial.

  17. Pabzt:

    this is what i use:

    I style my element in a way i want to display it and add display:none:

    #menu {
     display: none;
     /* other styles... */

    Then I create a keyframe animation

    @keyframes animation{
    	0% { opacity: 0; }
    	100% {opacity: 1;}

    Add a class for the animation

    .someClass {
    animation-name: animation;
    	animation-duration: 0.1s;
    	animation-iteration-count: 1;
    	animation-timing-function: ease-in;

    I created a jquery function to handle display none which gets the timeout from the animation class and also has a callback function for after animation stuff…

    $.fn.css3In = function(a, args)
    	return this.each(function()
    		$(this).css('display', 'block').addClass(a);
    		var duration = $('.' + a).css('animation-duration');
    		duration = duration.substring(0, duration.length-1) * 1000;
    		var object = $(this);
    		setTimeout(function() {
    		}, duration);

    Then I’m able to use any css3 Animation with keyframes on any object that has display none

    $('#menu').css3In('someClass', { AnimationEnd:function() {
            //Do Something after animation

    from display block to display none i use another jQuery function that does basicly the same:

    $.fn.css3Out = function(a, args)
    	return this.each(function()
    		console.log("out animation running...");
    		var duration = $('.' + a).css('animation-duration');
    		duration = duration.substring(0, duration.length-1) * 1000;
    		var object = $(this);
    		setTimeout(function() {
    			$(object).css('display', 'none');
    		}, duration);
  18. Alvin:

    Very nice tutorial!
    is it possible to animate the button to slide up after the box’s animation?

  19. Lurker:

    You may use ‘pointer-events:none’ with opacity to achieve the effects but with some fallback in >IE10.

  20. Eva:

    we always look for nice transition techniques like this one. Thank you for the idea.

  21. Sérgio Santana:

    A beter way i thing


    height: 0;
    transition: opacity 0.3s



    height: 200px;


    that code make just de opacity has transition :)

  22. Thanks for the useful article, it works perfectly.

  23. Well if I have to still use JavaScript… Why not use jQuery fadeIn and fadeOut function

    • Yep, that’s the simplest way, but the idea here was to try to figure out a way to do it with CSS performing the actual animation.

      And if I wrote this today, I’d probably avoid jQuery and use plain JS.

  24. Stumbled across this article with a similar problem. My solution is a little different from what I’ve seen and I’m cutting my teeth on JS.

    I wrapped the hidden content in a div with ‘overflow: hidden’ and set its height to 1px (so I can calculate the height of the hidden content). When the click triggers, I set the wrapper height to the content’s height and fade in the content.

    This way, CSS height animations still work, since the pixel value is set.

  25. Aamir Suleman:

    I would change the absolute position to be offscreen when the hidden class is applied

    .hidden {
    opacity: 0 !important;
    top:-1000px !important;

  26. Odin:

    Hey, I really need help making this method work the other way around. So the initial div is hidden and by clicking a button I want it to fade in and then fade out (toggling). I want to use the method for an overlay menu.

    Can you help me tweak the code?

    • Can you provide a demo of what you have so far? Also, doesn’t my final demo do exactly what you just said? I’m not sure what exactly you require beyond that. You might have to be more specific.

      • Odin:

        Ok, so, in your demo the div which the effect is applied on is initially visible whereas in my example the div is initially hidden. Imagine that I need to apply it on an overlay menu of a website and obviously on a website usually the overlay menu (which occupies the whole viewport) is initially hidden and then if you click a button (in my case the classic hamburger icon) it opens(fades in) and if you click it again it closes(fades out) and so on.

        Conclusion: I basically need what your demo does but the other way around (from hidden to visible to start with instead of from visible to hidden to start with).

        I managed to make it work by doing a workaround manually adding both classes “visuallyhidden” and “hidden” on the overlay menu div to start with. Example.

        <div id="menu_wrapper" class="hidden visuallyhidden">
              <!-- Menu Content Here-->

        This is how I have the code in the HTML page to start with (it works) but.. I best believe this is not a best practice. I was wondering how do I tweak the code to make it work strictly with jQuery and CSS without manually adding “visuallyhidden” and “hidden”.

        Let me know if this makes sense and if you still need me to provide a demo. Thanks a ton!

        • Based on my code from this article, what you’ve done is correct. That’s basically the only way to do it. Now, that being said, this is a bit of a hacky solution. But if that’s what you want, then yes, you just have to adjust the HTML classes so that it starts hidden.

          • Odin:

            If it requires a whole new function then I’ll just leave it as is. I tried manipulating the code but I couldn’t accomplish anything in my favor. I tried switching the classes from visuallyhidden to visuallyshown with "opacity:1" and hidden to shown with "display:block", I added "display:none" to the #menu_wrapper but I always ended up with the the function adding the visuallyshown class but not the shown class and then the function fell apart.

            Thanks again for the quick reply and keep up the great work!

  27. rsm:

    Thanks for the post, I was able to create my own javascript for my website!

  28. Fred:

    I typically use css transition on max-height, which allows for a sliding open/close of an element.

  29. Amod Oke:

    How can I use this to “show” a div instead of using toggle.
    Meaning I have this layout:

    <div class="1">First div</div>
    <div class="2">Second div</div>
    <div class="3">Second div</div>
    <div class="4">Second div</div>

    I already have a script that ‘fades in’ the opacity to show the divs as a user scrolls to it, but I want to hide them with display:none so that the browser’s scroll bar looks small initially…

    … so when the user scrolls to the concerned div, only then it shows up and ‘adds’ to the scroll bar.

    Here is my demo of the scrolling opacity toggler:</a.

  30. Pawel:

    Try this:

    var checkDisplay = function() {
    	if($('.your-selector').css('display') !== 'block') {
    		setTimeout(checkDisplay, 10);
    	} else {
  31. Tim:

    Thanks for sharing! I was getting obscure issue with the content on iOS. Was working on my Android phone though (ie. content that was set to opacity: 0 and visibility: hidden)

  32. IniCou:

    The below combination of JQuery’s “addClass & removeClass” (or Angularjs’s “ng-class”) along with the CSS @kayframe animation works perfectly for me in Chrome browser (did not check with other browsers)

    @keyframes showIn {
    from { opacity: 0; }
    to { opacity: 1; }
    .myElement {
    display: none;
    color: #ddd;
    background: #000;
    width: 200px;
    padding: 24px;
    margin: 0 auto;
    display: block;
    animation: showIn 1s ease-in forwards;
    <div ng-model=”showMyElement” ng-init=”showMyElement=false” style=”padding:30px”>
    <p class=”myElement” ng-class=”{‘fadeIn’:showMyElement}”>Click the below [button] to display an HTML element animatically</p>
    <button ng-click=”showMyElement = !showMyElement”>Click To <span ng-if=”!showMyElement”>Show</span><span ng-if=”showMyElement”>Hide</span></button>

  33. Type-D:

    Hmm, isn’t it easier to just use “position: absolute” on the element so it doesn’t take up any space? You can also place it in another container if you like. So something like in CSS:

    #container {
    position: absolute;
    left: 200px;
    top: 200px;
    .inner-item {
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transition:opacity 0.5s linear;
    } {
    opacity: 1;


    So every time you change the active element, it does the trick:
    $(“div#container>div.inner-item:nth-child([index you want to activate])”).addClass(“active”);

  34. Clay:

    So this won’t work for everyone, but it solved my problem.
    I wanted to hide links in the header when scrolled by adding a class with a css animation attached to it. I was able to hide the link by setting opacity to 0 but users could still click on it if they hovered over it. So, I just set the font size to 0 as well so the links wouldn’t show up, and wouldn’t take up any space. This only works with text obviously, but I’m thinking setting the height to 0 may produce a similar effect. The css looked like this:

    @keyframes fade-out {
    0% {opacity: 1; font-size: 1em;}
    100% {opacity: 0; font-size: 0;}

    .header-link-fade {
    animation-name: fade-out;
    animation-duration: .4s;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;

    Then I just toggled the class with jQuery with the link selector:

    $(window).scroll(function() {
      var scrollTop = $(this).scrollTop();
         $(".header-link-selector").toggleClass("header-link-fade", scrollTop>20);
  35. JP:

    The link to Christian Heilmann’s solution is broken. Here is the correct link:

  36. Evgeny:

    Great post! Thank you so much!

  37. Amaury:

    Thank you very much, it’s work great for me :)

  38. Qube:

    I just used width: 0; overflow: hidden; on element when hidden and width: 100%; when visible

  39. Felip:

    Hello everybody,

    I like the initial idea about dispplay:none, and i found a way to use it in addition to use a fade with opacity. It’s based on a listener to know when an element has ended its transition.

    I use this:

    #loadingWrapper {
    transition: all 1s ease-out;


    function loadingWrapperOut() {
    document.getElementById("loadingWrapper ").style.display = "none";

    window.addEventListener("load", () => {
    document.getElementById("loadingWrapper ").addEventListener("transitionend", loadingWrapperOut);

    document.getElementById("loadingWrapper ").style.opacity = "0";


  40. Rick:


    Great post and a lot of useful comments.

    Wonder if anyone could help me? So I have taken one of the solutions and trying to make it hide a div but the button stay where it is and not slide with the hidden div.

    Here is a fiddle of it:

    Any help will be gratefully appreciated.

    • If you want the button to stay there, then you have to change the hiddenContent section to be like this:

      .hiddenContent {
        opacity: 0;

      You have to remove the height and margin. But I’m not sure if that’s what you want, because your comment wasn’t that clear.

      • Rick:

        Thanks Louis. Very much appreciated you understood my poor question perfectly and yes thats exactly what I wanted. Thank you!

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field. If you use keywords, your comment will be deleted, or your name will be replaced with the alias from your email address. No foul language, please. Thank you for cooperating.

Instructions for code snippets: Wrap inline code in <code> tags; wrap blocks of code in <pre> and <code> tags. When you want your HTML to display on the page in a code snippet inside of <code> tags, make sure you use &lt; and &gt; instead of < and >, otherwise your code will be eaten by pink unicorns.