Categories
css css-transitions

Transitions on the CSS display property

1798

I’m currently designing a CSS ‘mega dropdown’ menu – basically a regular CSS-only dropdown menu, but one that contains different types of content.

At the moment, it appears that CSS 3 transitions don’t apply to the ‘display’ property, i.e., you can’t do any sort of transition from display: none to display: block (or any combination).

Is there a way for the second-tier menu from the above example to ‘fade in’ when someone hovers over one of the top level menu items?

I’m aware that you can use transitions on the visibility: property, but I can’t think of a way to use that effectively.

I’ve also tried using height, but that just failed miserably.

I’m also aware that it’s trivial to achieve this using JavaScript, but I wanted to challenge myself to use just CSS, and I think I’m coming up a little short.

11

  • 26

    position: absolute; visibility: hidden; is same as display: none;

    – Jawad

    Sep 16, 2012 at 16:31

  • 12

    @Jawad: Only if you add something like z-index:0 as well.

    – DanMan

    Oct 31, 2012 at 9:52

  • 23

    @Jawad: It’s recommended to never use visibility: hidden unless you want screenreaders to read it (whereas typical browsers won’t). It only defines the visibility of the element (like saying opacity: 0), and it’s still selectable, clickable, and whatever it used to be; it’s just not visible.

    May 19, 2013 at 23:14

  • 2

    no support for pointer-events in IE 8,9,10, so it’s not always ok

    Feb 20, 2015 at 14:08

  • 3

    You need to display: none otherwise you’ll be stumbling into the hidden object outside the trigger and it’ll be showing accidentally… I’m just saying 🙂

    Dec 24, 2015 at 0:08

1637

You can concatenate two transitions or more, and visibility is what comes handy this time.

div {
  border: 1px solid #eee;
}
div > ul {
  visibility: hidden;
  opacity: 0;
  transition: visibility 0s, opacity 0.5s linear;
}
div:hover > ul {
  visibility: visible;
  opacity: 1;
}
<div>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>
</div>

(Don’t forget the vendor prefixes to the transition property.)

More details are in this article.

7

  • 33

    Yeah the problem with this is anything behind it will overlap even if it’s not visible. I found using height:0 a much better solution

    – Josh Bedo

    Sep 2, 2013 at 23:34

  • 940

    This is nice but the problem is that “visibility hidden” elements still occupy space while “display none” does not.

    Nov 14, 2013 at 10:48

  • 49

    I’m probably missing something, but why do you alter both the visibility AND the opacity? Won’t setting the opacity to 0 hide the element – why do you need to set the visibility to hidden too?

    – GMA

    Jul 3, 2014 at 11:14

  • 28

    @GeorgeMillo if you set only the opacity, the element is actually still on the page rendering (you can’t click thought for example).

    Aug 26, 2014 at 19:37

  • 30

    This should not be marked as the correct answer. It does not deal with the display property and as Rui said, the element still takes up space making it impractical for many situations.

    Aug 6, 2015 at 7:40

926

You need to hide the element by other means in order to get this to work.

I accomplished the effect by positioning both <div>s absolutely and setting the hidden one to opacity: 0.

If you even toggle the display property from none to block, your transition on other elements will not occur.

To work around this, always allow the element to be display: block, but hide the element by adjusting any of these means:

  1. Set the height to 0.
  2. Set the opacity to 0.
  3. Position the element outside of the frame of another element that has overflow: hidden.

There are likely more solutions, but you cannot perform a transition if you toggle the element to display: none. For example, you may attempt to try something like this:

div {
    display: none;
    transition: opacity 1s ease-out;
    opacity: 0;
}
div.active {
    opacity: 1;
    display: block;
}

But that will not work. From my experience, I have found this to do nothing.

Because of this, you will always need to keep the element display: block – but you could get around it by doing something like this:

div {
    transition: opacity 1s ease-out;
    opacity: 0;
    height: 0;
    overflow: hidden;
}
div.active {
    opacity: 1;
    height: auto;
}

2

  • 35

    Thanks Jim for a thorough answer. You’re absolutely right about the fact that if the display: property changes at all, then ALL of your transitions will not work. Which is a shame – I wonder what the reasoning behind that is. On a side note, on the same link I posted in the original question, you can see where I’m at with it. The only (small) problem I have is in Chrome [5.0.375.125] when the page loads, you can see the menu quickly fading away as the elements are loaded on the page. Firefox 4.0b2 and Safari 5.0 are absolutely fine… bug or something I’ve missed?

    Jul 28, 2010 at 19:45

  • I implemented this, then it didn’t work, then I realised that what your saying is this won’t work. This answer assumes were not all just speed reading the important bits… 🙂

    – Liam

    Apr 26 at 10:03

350

At the time of this post all major browsers disable CSS transitions if you try to change the display property, but CSS animations still work fine so we can use them as a workaround.

Example Code (you can apply it to your menu accordingly) Demo:

Add the following CSS to your stylesheet:

@-webkit-keyframes fadeIn {
    from { opacity: 0; }
      to { opacity: 1; }
}
@keyframes fadeIn {
    from { opacity: 0; }
      to { opacity: 1; }
}

Then apply the fadeIn animation to the child on parent hover (and of course set display: block):

.parent:hover .child {
    display: block;
    -webkit-animation: fadeIn 1s;
    animation: fadeIn 1s;
}

Update 2019 – Method that also supports fading out:

(Some JavaScript code is required)

// We need to keep track of faded in elements so we can apply fade out later in CSS
document.addEventListener('animationstart', function (e) {
  if (e.animationName === 'fade-in') {
      e.target.classList.add('did-fade-in');
  }
});

document.addEventListener('animationend', function (e) {
  if (e.animationName === 'fade-out') {
      e.target.classList.remove('did-fade-in');
   }
});
div {
    border: 5px solid;
    padding: 10px;
}

div:hover {
    border-color: red;
}

.parent .child {
  display: none;
}

.parent:hover .child {
  display: block;
  animation: fade-in 1s;
}

.parent:not(:hover) .child.did-fade-in {
  display: block;
  animation: fade-out 1s;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
<div class="parent">
    Parent
    <div class="child">
        Child
    </div>
</div>

7

  • 3

    Thanks for this. The height: 0 trick (for transitions) mentioned above doesn’t seem to work because the height gets set to 0 on the fade-out transition, but this trick seems to work just fine.

    Mar 14, 2012 at 20:42

  • 46

    Thanks, very useful. But how to fade it out?

    – Illiou

    Oct 15, 2012 at 2:37

  • awesome, but when i hover the DIV while the animation runs it flickers (changes opacity to a lower state) … any idea?

    – user1688793

    Jul 1, 2014 at 10:25

  • 2

    The first paragraph of this answer doesn’t quite make sense. Browsers don’t just disable all transitions outright the moment you use the display property – there is really no reason to. And even if they did, why would animations work then? You can’t use the display property in CSS animations either.

    – BoltClock

    Jul 11, 2014 at 10:17


  • 1

    Yeah, “change” – I’m not sure why I said “use” there. My point is you can’t transition or animate display, but that doesn’t prevent all other properties from animating either so long as you’re not transitioning to none.

    – BoltClock

    Jul 11, 2014 at 14:17