Dialog (Message Dialog)

This page shows a message 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.



Message Dialog

This is the description for the dialog

Message dialogs can contain long (or short) passages of text, such as terms of use notices, user agreements for software, help text, explanations, etc.

Initial HTML Markup

<div class="deque-dialog-message" id="deque-dialog-message" data-is-modal="true" data-tabtrap>
  <div class="deque-dialog-screen-wrapper"></div>
  <div class="deque-dialog-screen">
    <h1 id="dialogMessageHeading" class="deque-dialog-heading">Message Dialog</h1>
    <p id="dialogMessageDescription" class="deque-dialog-description">This is the description for the dialog</p>
    <div role="document" class="deque-dialog-content">
      <p>Message dialogs can contain long (or short) passages of text, such as terms of use notices, user agreements for software, help text, explanations, etc.</p>
    </div>
    <p class="deque-dialog-buttons">
      <button class="deque-button">Continue</button>
      <button class="deque-button">Cancel</button>
      <button class="deque-dialog-button-close" aria-label="Close dialog"><span aria-hidden="true"></span></button>
    </p>
  </div>
</div>

<div>
  <button id="deque-dialog-message-trigger" class="deque-dialog-message-trigger deque-button" aria-controls="deque-dialog-message">Show Message 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-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-message" 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-message", 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" and because this is a dialog message box the container should be a <div> with role="document".
      • In this example we included a <p> container with the content.
    • 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-message". The inner text is displayed on the button.