Dialog (Simple Dialog)

This page shows a simple modal pattern using the (role="dialog") ARIA attribute. A modal is a dialog box/popup window that is displayed on top of the current page and requires a user action to close it. The dialog is only available to users when the modal is active. When the modal is active, the rest of the page is unavailable by mouse, keyboard, touch, or screen reader.



Simple Dialog

Please enter your first and last name.

Initial HTML Markup

<div id="deque-dialog" class="deque-dialog">
  <div class="deque-dialog-screen-wrapper"></div>
  <div class="deque-dialog-screen">
    <h1 id="dialogHeading" class="deque-dialog-heading">Simple Dialog</h1>
    <p class="deque-dialog-description" id="dialogDescription">Please enter your first and last name.</p>
    <form class="deque-dialog-content">
      <label for="nameInput">First Name</label>
      <input class="deque-input" id="nameInput" type="text" data-trap-enter="true">
      <label for="lastNameInput">Last Name</label>
      <input class="deque-input" id="lastNameInput" type="text" data-trap-enter="true">
    </form>
    <div class="deque-dialog-buttons">
      <button class="deque-button">Submit</button>
      <button class="deque-button">Cancel</button>
      <button class="deque-dialog-button-close" aria-label="Close dialog"><span aria-hidden="true"></span></button>
    </div>
  </div>
</div>
<div>
  <button id="deque-dialog-trigger" class="deque-dialog-trigger deque-button" aria-controls="deque-dialog">Show Dialog!</button>
</div>

JavaScript

Required: The complete JavaScript file (for all patterns in the library): deque-patterns.min.js

Key parts of the JavaScript file

Note: The code below is functional only in the context of the complete JavaScript file.

In the @dialog section:


	function createDialog(id, role) {
	  var screen = (0, _lightboxScreen.createScreen)();
	  var alert = document.getElementById(id);
	  var dialogButtons = alert.querySelector('.deque-dialog-buttons');
	  var buttons = dialogButtons.querySelectorAll('button');
	  for (var x = 0; x < buttons.length; x++) {
	    buttons[x].addEventListener('click', hide);
	  }
	
	  // close button
	  var xButton = document.createElement('span');
	
	  function setInitialFocus(config) {
	    if (!config.isAlert) {
	      var target = (0, _focusUtils.getFirstFocusableChild)(alert);
	      if (target.classList.contains('deque-dialog-button-close')) {
	        target = (0, _focusUtils.getNextFocusableChild)(alert, target);
	      }
	
	      if (target) {
	        return target.focus();
	      }
	    }
	
	    if (config.isAlert && config.isDetail) {
	      return content.focus();
	    }
	
	    if (buttonBar.getFirstButton()) {
	      return buttonBar.getFirstButton().focus();
	    }
	
	    alert.focus();
	  }
	
	  function reset() {
	    clearClasses();
	    screen.clear();
	  }
	
	  function addClasses(classes) {
	    if (classes.isArray) {
	      classes.forEach(function (item) {
	        return alert.classList.add(item);
	      });
	    } else {
	      alert.classList.add(classes);
	    }
	  }
	
	  function clearClasses() {
	    var toRemove = [];
	    for (var i = 0; i < alert.classList.length; i++) {
	      toRemove.push(alert.classList[i]);
	    }
	
	    toRemove.forEach(function (c) {
	      alert.classList.remove(c);
	    });
	
	    addClasses(role);
	  }
	
	  function setRole(role) {
	    alert.setAttribute('role', role);
	  }
	
	  function hideCloseButton() {
	    xButton.classList.add('deque-hidden');
	  }
	
	  function showCloseButton() {
	    xButton.classList.remove('deque-hidden');
	  }
	
	  var content = (0, _contentArea.createContentArea)(alert);
	
	  var returnFocusTo;
	
	  xButton.addEventListener('click', handleEscape);
	
	  var buttonBar = alert.querySelector('.deque-dialog-buttons');
	  buttonBar = (0, _buttonBar.createButtonBar)(buttonBar);
	
	  function show(config) {
	
	    if (config.classes) {
	      addClasses(config.classes);
	    }
	
	    if (config.describedby) {
	      alert.setAttribute('aria-describedby', config.describedby);
	    }
	
	    if (config.labelledby) {
	      alert.setAttribute('aria-labelledby', config.labelledby);
	    }
	
	    if (config.hideCloseButton) {
	      hideCloseButton();
	    } else {
	      showCloseButton();
	    }
	
	    if (config.wrapperID) {
	      document.getElementById(config.wrapperID).setAttribute('aria-hidden', 'true');
	    }
	
	    config.isAlert ? setRole('alertdialog') : setRole('dialog');
	    screen.show();
	    document.body.appendChild(alert);
	    alert.setAttribute('aria-hidden', 'false');
	    alert.classList.remove('deque-hidden');
	    alert.classList.add('deque-show-block');
	
	    setInitialFocus(config);
	  }
	
	  function hide() {
	
	    if (alert.hasAttribute('aria-describedby')) {
	      alert.removeAttribute('aria-describedby');
	    }
	
	    reset();
	    alert.setAttribute('aria-hidden', 'true');
	    alert.classList.add('deque-hidden');
	
	    returnFocusTo.focus();
	  }
	
	  function handleEscape() {
	
	    hide();
	  }
	
	  function keyUpHandler(e) {
	    if (e.which === 27) {
	      handleEscape();
	      e.stopPropagation();
	    }
	  }
	
	  alert.addEventListener('keyup', keyUpHandler);
	
	  (0, _focusUtils.initTabTrap)(alert);
	
	  return function (triggerElement, config) {
	    returnFocusTo = triggerElement;
	
	    // make sure we never end up with a case where a non-alert box is treated
	    // as a simple alert.
	    if (!config.isAlert) {
	      config.isDetail = true;
	    }
	
	    show(config);
	  };
	}
	
	/*
	* activateAllDialogs(): This method has 4 types of Dialogs.
	* Simple Dialogs, Dialog Alerts, Dialog Messages and Dialog Messages Alerts
	* 1. Looks for all instances of respective classes and loops them over 
	* with all details like description, headings and stores them in config class.
	* 2. For each button click, it gets the value of aria-control and creates and shows the respective dialog based on the config values.
	*/
	function activateAllDialogs() {
	
	  //activate all simple Dialogs
	  var dialogs = document.querySelectorAll('.deque-dialog');
	
	  for (var i = 0; i < dialogs.length; i++) {
	    dialogs[i].classList.add('deque-hidden');
	
	    var dialogDescription;
	    var dialogLabel;
	
	    if (dialogs[i].querySelector('.deque-dialog-description')) {
	      dialogDescription = dialogs[i].querySelector('.deque-dialog-description').id;
	    }
	    if (dialogs[i].querySelector('.deque-dialog-heading')) {
	      dialogLabel = dialogs[i].querySelector('.deque-dialog-heading').id;
	    }
	
	    var configDialog = {
	      describedby: dialogDescription,
	      labelledby: dialogLabel,
	      isAlert: false
	    };
	
	    var triggerDialog = document.getElementsByClassName('deque-dialog-trigger deque-button');
	
	    triggerDialog[i].addEventListener('click', function (event) {
	      event.preventDefault();
	      var attributeDialog = this.getAttribute('aria-controls');
	      var showDialog = createDialog(attributeDialog, 'deque-dialog');
	      showDialog(this, configDialog);
	    });
	  }
	
	  //activate all dialog alerts  
	  var dialogAlerts = document.querySelectorAll('.deque-dialog-alert');
	  for (var j = 0; j < dialogAlerts.length; j++) {
	    dialogAlerts[j].classList.add('deque-hidden');
	
	    var dialogAlertDescription;
	    var dialogAlertLabel;
	    if (dialogAlerts[j].querySelector('.deque-dialog-description')) {
	      dialogAlertDescription = dialogAlerts[j].querySelector('.deque-dialog-description').id;
	    }
	    if (dialogAlerts[j].querySelector('.deque-dialog-heading')) {
	      dialogAlertLabel = dialogAlerts[j].querySelector('.deque-dialog-heading').id;
	    }
	
	    var configDialogAlert = {
	      describedby: dialogAlertDescription,
	      labelledby: dialogAlertLabel,
	      isAlert: true
	    };
	
	    var triggerDialogAlert = document.getElementsByClassName('deque-dialog-alert-trigger deque-button');
	
	    triggerDialogAlert[j].addEventListener('click', function (event) {
	      event.preventDefault();
	      var attributeDialogAlert = this.getAttribute('aria-controls');
	      var showDialogAlert = createDialog(attributeDialogAlert, 'deque-dialog-alert');
	      showDialogAlert(this, configDialogAlert);
	    });
	  }
	
	  //activate all dialog messages
	  var dialogMessages = document.querySelectorAll('.deque-dialog-message');
	  for (var k = 0; k < dialogMessages.length; k++) {
	    dialogMessages[k].classList.add('deque-hidden');
	
	    var dialogMessageDescription;
	    var dialogMessageLabel;
	    if (dialogMessages[k].querySelector('.deque-dialog-description')) {
	      dialogMessageDescription = dialogMessages[k].querySelector('.deque-dialog-description').id;
	    }
	    if (dialogMessages[k].querySelector('.deque-dialog-heading')) {
	      dialogMessageLabel = dialogMessages[k].querySelector('.deque-dialog-heading').id;
	    }
	
	    var configDialogMessage = {
	      describedby: dialogMessageDescription,
	      labelledby: dialogMessageLabel,
	      isAlert: false
	    };
	
	    var triggerDialogMessage = document.getElementsByClassName('deque-dialog-message-trigger deque-button');
	
	    triggerDialogMessage[k].addEventListener('click', function (event) {
	      event.preventDefault();
	      var attributeDialogMessage = this.getAttribute('aria-controls');
	      var showDialogMessage = createDialog(attributeDialogMessage, 'deque-dialog-message');
	      showDialogMessage(this, configDialogMessage);
	    });
	  }
	
	  //activate all diaglog message alerts
	  var dialogMessageAlerts = document.querySelectorAll('.deque-dialog-message-alert');
	  for (var l = 0; l < dialogMessageAlerts.length; l++) {
	    dialogMessageAlerts[l].classList.add('deque-hidden');
	
	    var dialogMessageAlertDescription;
	    var dialogMessageAlertLabel;
	    if (dialogMessageAlerts[l].querySelector('.deque-dialog-description')) {
	      dialogMessageAlertDescription = dialogMessageAlerts[l].querySelector('.deque-dialog-description').id;
	    }
	    if (dialogMessageAlerts[l].querySelector('.deque-dialog-heading')) {
	      dialogMessageAlertLabel = dialogMessageAlerts[l].querySelector('.deque-dialog-heading').id;
	    }
	    var configDialogMessageAlert = {
	      describedby: dialogMessageAlertDescription,
	      labelledby: dialogMessageAlertLabel,
	      isAlert: true
	    };
	
	    var triggerDialogMessageAlert = document.getElementsByClassName('deque-dialog-message-alert-trigger deque-button');
	
	    triggerDialogMessageAlert[l].addEventListener('click', function (event) {
	      event.preventDefault();
	      var attributeDialogMessageAlert = this.getAttribute('aria-controls');
	      var showDialogMessageAlert = createDialog(attributeDialogMessageAlert, 'deque-dialog-message-alert');
	      showDialogMessageAlert(this, configDialogMessageAlert);
	    });
	  }
	}
	activateAllDialogs();
	

In the @focusUtils section:


	var focusableQuery = 'input:not([tabindex^="-"]), select:not([tabindex^="-"]), textarea:not([tabindex^="-"]), button:not([tabindex^="-"]), object:not([tabindex^="-"]), [href]:not([tabindex^="-"]), [tabindex]:not([tabindex^="-"]):not(.tabtrap)';
	
	function getFirstFocusableChild(element) {
	  return element.querySelector(focusableQuery);
	}
	
	function getNextFocusableChild(element, current) {
	  var all = (0, _selectionUtils.queryAll)(focusableQuery);
	  var targetReturnIndex = all.indexOf(current) + 1;
	
	  if (targetReturnIndex <= all.length - 1) {
	    return all[targetReturnIndex];
	  }
	
	  return null;
	}
	
	function getLastFocusableChild(element) {
	  var all = element.querySelectorAll(focusableQuery);
	  return all[all.length - 1];
	}
	
	function initTabTrap(element) {
	  function createTrap() {
	    var trap = document.createElement('div');
	    trap.classList.add('tabtrap');
	    trap.setAttribute('tabindex', '0');
	
	    return trap;
	  }
	
	  function applyTraps(element, firstTrap, lastTrap) {
	    firstTrap.addEventListener('focus', function () {
	      getLastFocusableChild(element).focus();
	    });
	
	    lastTrap.addEventListener('focus', function () {
	      getFirstFocusableChild(element).focus();
	    });
	
	    element.insertBefore(firstTrap, element.firstChild);
	    element.appendChild(lastTrap);
	  }
	
	  var firstTrap = createTrap();
	  var lastTrap = createTrap();
	
	  applyTraps(element, firstTrap, lastTrap);
	}
	

In the @dialog-header section:

In the @dialog-contentArea section:


	
	function renderContent(contentArea, message) {
	  if (message.substr) {
	    var p = document.getElementById('deque-dialog-description');
	    p.innerText = message;
	    contentArea.appendChild(p);
	  } else {
	    contentArea.appendChild(message);
	  }
	}
	
	function clearContent(contentArea) {
	  contentArea.innerText = '';
	}
	
	function createContentArea(alert) {
	  var contentArea = alert.querySelector('.deque-dialog-content');
	  contentArea.setAttribute('role', 'document');
	  contentArea.setAttribute('tabindex', '-1');
	
	  contentArea.render = renderContent.bind(null, contentArea);
	  contentArea.clear = clearContent.bind(null, contentArea);
	
	  return contentArea;
	}
	

In the @dialog-buttonBar section:


	function getFirstButton(buttonBar) {
	  return buttonBar.children[0] || null;
	}
	
	function getLastButton(buttonBar) {
	  var buttons = buttonBar.children;
	  return buttons[buttons.length - 1] || null;
	}
	
	// IE has issues with this section, so commenting it out for now. See DU-2071 for more details.
	// function clearButtons(buttonBar) {
	//  try {
	//    buttonBar.innerHTML = '';
	//  } catch (e) { /* ignore error */ }
	// }
	
	function initializeButtonBar(buttonBar, config, hide) {
	  clearButtons(buttonBar);
	
	  formatButtons(config, hide).forEach(function (button, index, buttons) {
	    buttonBar.appendChild(button.button);
	
	    if (index < buttons.length - 1) {
	      var space = document.createElement('span');
	      space.innerHTML = '';
	      buttonBar.appendChild(space);
	    }
	  });
	}
	
	function formatButtons(config, hide) {
	  return config.map(function (buttonConfig) {
	    var button;
	
	    if (buttonConfig.markup) {
	      var wrapper = document.createElement('div');
	      wrapper.innerHTML = buttonConfig.markup;
	      button = wrapper.querySelector('button');
	      wrapper = null;
	    } else {
	      button = document.createElement('button');
	      button.innerText = buttonConfig.label;
	    }
	
	    if (buttonConfig.classes) {
	      buttonConfig.classes.forEach(function (c) {
	        return button.classList.add(c);
	      });
	    }
	
	    var handler = function handler() {
	      if (buttonConfig.preClose) {
	        buttonConfig.preClose();
	      }
	
	      hide();
	
	      if (buttonConfig.postClose) {
	        buttonConfig.postClose();
	      }
	    };
	
	    button.addEventListener('click', handler);
	
	    return {
	      button: button,
	      handler: handler
	    };
	  });
	}
	
	function createButtonBar(buttonBar) {
	  //  var buttonBar = alert.querySelector('.deque-dialog-buttons');
	
	  buttonBar.getFirstButton = getFirstButton.bind(null, buttonBar);
	  buttonBar.getLastButton = getLastButton.bind(null, buttonBar);
	  // buttonBar.clear = clearButtons.bind(null, buttonBar);
	  buttonBar.initialize = initializeButtonBar.bind(null, buttonBar);
	
	  return buttonBar;
	}
	

In the @dialog-lightboxScreen section:


	function clearScreen(screen) {
	  screen.remove();
	}
	
	function showScreen(screen) {
	  document.body.appendChild(screen);
	}
	
	function createScreen() {
	  var screen = document.createElement('div');
	  // screen.classList.add('deque-dialog-screen');
	  screen.show = showScreen.bind(null, screen);
	  screen.clear = clearScreen.bind(null, screen);
	
	  return screen;
	}
	

In the @selectionUtils section:


	function queryAll(selector, context) {
	  context = context || document;
	  return (0, _collectionUtils.toArray)(context.querySelectorAll(selector));
	}
	

In the @collectionUtils section:


	function toArray(arraylike) {
	  return Array.prototype.slice.call(arraylike);
	}
	

Note: No additional JavaScript initialization code is necessary for this pattern. All elements with class="deque-dialog" will be initialized automatically by the external JavaScript file.

CSS

Required: The complete CSS file (for all patterns in the library): deque-patterns.min.css

Key styles within the CSS file (other styles may also be necessary):


.deque-dialog-buttons button {
  cursor: pointer;
  color: #ffffff;
  background-color: #006cc1;
  font-size: 15px;
  max-width: 374px;
  min-width: 120px;
  display: inline-block;
  padding: 9px 12px 10px;
  border: solid 1px transparent;
  border-radius: 0;
  overflow: hidden;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: bottom;
  outline: none;
}
.deque-dialog-buttons button:hover {
  cursor: default;
}
.deque-dialog-trigger {
  cursor: pointer;
  background-color: #006cc1;
  font-size: 15px;
  max-width: 374px;
  min-width: 120px;
  display: inline-block;
  margin-top: 0;
  padding: 9px 12px 10px;
  border: solid 1px transparent;
  border-radius: 0;
  overflow: hidden;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: bottom;
  outline: none;
}
.deque-dialog-alert-trigger {
  cursor: pointer;
  background-color: #006cc1;
  font-size: 15px;
  max-width: 374px;
  min-width: 120px;
  display: inline-block;
  margin-top: 0;
  padding: 9px 12px 10px;
  border: solid 1px transparent;
  border-radius: 0;
  overflow: hidden;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: bottom;
  outline: none;
}
.deque-dialog-message-trigger {
  cursor: pointer;
  background-color: #006cc1;
  font-size: 15px;
  max-width: 374px;
  min-width: 120px;
  display: inline-block;
  margin-top: 0;
  padding: 9px 12px 10px;
  border: solid 1px transparent;
  border-radius: 0;
  overflow: hidden;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: bottom;
  outline: none;
}
.deque-dialog-message-alert-trigger {
  cursor: pointer;
  background-color: #006cc1;
  font-size: 15px;
  max-width: 374px;
  min-width: 120px;
  display: inline-block;
  margin-top: 0;
  padding: 9px 12px 10px;
  border: solid 1px transparent;
  border-radius: 0;
  overflow: hidden;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: bottom;
  outline: none;
}
.deque-dialog .deque-dialog-heading,
.deque-dialog-alert .deque-dialog-heading,
.deque-dialog-message .deque-dialog-heading,
.deque-dialog-message-alert .deque-dialog-heading {
  font-size: 20px;
  color: #2f2f2f;
  font-weight: 200;
  line-height: normal;
  padding: 0;
}
.deque-dialog.deque-dialog-noheading,
.deque-dialog-alert.deque-dialog-noheading,
.deque-dialog-message.deque-dialog-noheading,
.deque-dialog-message-alert.deque-dialog-noheading {
  text-align: center;
  font-size: 1.1em;
}
.deque-dialog-notify {
  background-color: #ff0000;
  color: #ffffff;
}
.deque-wrapper .feedback-holder {
  background-color: #fefee7;
  border: 2px solid #f5f573;
  padding: 20px 40px;
  display: block;
}
.deque-wrapper .feedback-holder:empty {
  display: none;
}
.deque-wrapper form.deque[data-feedback-type] .feedback-holder {
  outline: none;
}
.deque-wrapper .container .feedback-holder .info {
  border-color: #000066;
  background-color: #eeeeff;
  border: 1px solid;
  border-radius: 5px;
  margin: 5px auto;
  padding: 5px;
}
.deque-wrapper .container .feedback-holder .error {
  border-color: #660000;
  background-color: #fff5f5;
  border: 1px solid #ff9494;
  margin: 5px auto;
  padding: 5px;
}
.deque-wrapper .container .feedback-holder .success {
  border-color: #006600;
  background-color: #eeffee;
  border: 1px solid;
  border-radius: 5px;
  margin: 5px auto;
  padding: 5px;
}
.deque-dialog-buttons .deque-dialog-button-close {
  font-family: 'mwf-glyphs';
  font-size: 20px;
  width: 20px;
  height: 20px;
  right: 8px;
  top: 8px;
  position: absolute;
  background-color: transparent;
  display: block;
  overflow: visible;
  max-width: 20px;
  min-width: 20px;
  padding: 0;
}
.deque-dialog-buttons .deque-dialog-button-close span::before {
  content: '\E711';
  color: #006cc1;
  display: inline-block;
  font-size: 20px;
  width: 20px;
  height: 20px;
}
.deque-dialog-buttons .deque-dialog-button-close:focus {
  outline: 1px dashed #000000;
}
.deque-show-block .deque-dialog-screen-wrapper {
  display: block;
  content: '';
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.7);
  transition: opacity 200ms;
  opacity: 1;
}
.deque-hidden .deque-dialog-screen-wrapper {
  display: none;
}
.deque-show-block .deque-dialog-screen {
  display: block;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: #ffffff;
  border: 1px solid #0078d7;
  margin: 0 auto;
  max-height: 760px;
  max-width: 546px;
  padding: 24px;
  z-index: 1000;
  outline: none;
  width: 80%;
  opacity: 1;
}
.deque-hidden .deque-dialog-screen {
  display: none;
}

Fonts

Note: You will need to edit the src for font-family:'mwf-glyphs' in the external CSS file.

Implementation Instructions

Step 1: Add Dependencies

Add deque-patterns.min.css in the <head> of the document

<link rel="stylesheet" type="text/css" href="deque-patterns.min.css"></li>

Add a script link to deque-patterns.min.js to the bottom of the page.

<script type="text/javascript" src="deque-patterns.min.js"></script>

Step 2: Add HTML

  • Wrap the HTML in a <div> or <span> container with class="deque-dialog", for styling purposes and to allow the javascript to initialize all the dialog boxes on the page.
    • Optional: Add a header to the dialog box in a <h1> container with class="deque-dialog-heading".
    • Optional: Add a description to the dialog box in a <p> container with class="deque-dialog-description".
    • The content of the dialog box needs to have the class="deque-dialog-content", but what container you use and what you put inside is up to what you want this dialog box to do. We used an example of a <form> container that is used to collect user input.
      • In this example we included a <p> container that surrounded each form question.
        • The <label> container has a "for" attribute that corresponds with the input field's ID.
        • Then create an <input> container with a unique ID, type="text", data-trap-enter="true", and class="deque-input" for stlying purposes.
    • The small X in the upper right hand corner is automatically created with the dialog box, but if you would like to create more buttons create a <div> or <span> container with class="deque-dialog-buttons".
      • Each button is in a <button> container with class="deque-button".
  • Create the trigger to activate the dialog box within a <div> container.
    • In this example we used a <button> to trigger the dialog with id="deque-dialog-trigger", class="deque-dialog-trigger deque-button", and aria-controls="deque-dialog". The inner text is displayed on the button.