htmlcssmodal-dialog

How do I CSS transition a modal dialog element when it opens?


I want to apply a transition to dialog elements when they are opened, but my transition attribute doesn't seem to be having any effect. Here's my code:

const dialog = document.querySelector("dialog");

document.querySelector("#open-button").addEventListener("click", () => {
  dialog.showModal();
});
document.querySelector("#close-button").addEventListener("click", () => {
  dialog.close();
});
button {
  display: block;
}

dialog {
  position: absolute;
  top: 50px;
  margin: auto;
  padding: 0;
  width: 50%;
  height: 50%;
  background-color: red;
  opacity: 0;
  -webkit-transition: opacity 2s ease-in, background-color 2s ease-in;
  -o-transition: opacity 2s ease-in, background-color 2s ease-in;
  transition: opacity 2s ease-in, background-color 2s ease-in;
}

dialog[open] {
  background-color: green;
  opacity: 1;
}
<button id="open-button">Open Dialog Element</button>

<dialog>
  <button id="close-button">Close Dialog Element</button>
</dialog>

My intent is to slowly fade the dialog into view as its background visibly changes from red to green.

Instead, the rules of dialog[open] are immediately applied? The MDN article doesn't mention transitions or animations anywhere, implying this should work as any other element.

I am testing this in Firefox on Windows 10.


Solution

  • Your dialog transitions from display:none to display:block and that ruins transition. The idea was to add dialog { display:block; } but it turned out bad because the dialog is just hidden and reacts to the keyboard TAB navigation for example... So we need to keep a closed dialog display:none.

    So a couple of solutions:

    1. You could use animation which I like since it's a pure CSS solution:
    dialog[open] {
      animation: fadein 2s ease-in forwards;
    }
    
    @keyframes fadein{
      0%{
        opacity:0;
      }
      100%{
        opacity:1;
        background-color: green;
      }
    }
    

    const dialog = document.querySelector("dialog");
    
    document.querySelector("#open-button").addEventListener("click", () => {
      dialog.showModal();
    });
    document.querySelector("#close-button").addEventListener("click", () => {
      dialog.close();
    });
    button {
      display: block;
    }
    
    dialog {
      position: absolute;
      top: 50px;
      margin: auto;
      padding: 0;
      width: 50%;
      height: 50%;
      background-color: red;
      opacity: 0;
    }
    
    dialog[open] {
      animation: fadein 2s ease-in forwards;
    }
    
    @keyframes fadein{
      0%{
        opacity:0;
      }
      100%{
        opacity:1;
        background-color: green;
      }
    }
    <button id="open-button">Open Dialog Element</button>
    
    <dialog>
      <button id="close-button">Close Dialog Element</button>
    </dialog>

    1. You could also add a CSS class after the opening with setTimeout to allow the DOM to be re-rendered and remove it on the closing with your transition left intact.
    setTimeout(()=>dialog.classList.add('open'));
    dialog.addEventListener('close', () => dialog.classList.remove('open'));
    

    The extra benefit of this approach that you can close the dialog with the transition also:

    document.querySelector("#close-button").addEventListener("click", () => {
      
      const close = e => e.propertyName === 'background-color' && 
        dialog.close() ||
        dialog.removeEventListener('transitionend', close);
        
      dialog.addEventListener('transitionend', close);
      dialog.classList.remove('open');
      
    });
    

    const dialog = document.querySelector("dialog");
    
    dialog.addEventListener('close', () => dialog.classList.remove('open'));
    
    document.querySelector("#open-button").addEventListener("click", () => {
      dialog.showModal();
      setTimeout(()=>dialog.classList.add('open'));
    });
    
    document.querySelector("#close-button").addEventListener("click", () => {
      
      const close = e => e.propertyName === 'background-color' && 
        dialog.close() ||
        dialog.removeEventListener('transitionend', close);
        
      dialog.addEventListener('transitionend', close);
      dialog.classList.remove('open');
      
    });
    button {
      display: block;
    }
    
    dialog {
      position: absolute;
      top: 50px;
      margin: auto;
      padding: 0;
      width: 50%;
      height: 50%;
      background-color: red;
      opacity: 0;
      -webkit-transition: opacity 2s ease-in, background-color 2s ease-in;
      -o-transition: opacity 2s ease-in, background-color 2s ease-in;
      transition: opacity 2s ease-in, background-color 2s ease-in;
    }
    
    dialog.open {
      background-color: green;
      opacity: 1;
    }
    <button id="open-button">Open Dialog Element</button>
    
    <dialog>
      <button id="close-button">Close Dialog Element</button>
    </dialog>