Lightbox Dialog

A modal window which dims out the main page behind it.
Screenshot of lightbox dialog on desktop screen.

Introduction

In traditional desktop operating systems, a lightbox dialog is a child-window that typically exists to communicate information to the user, display a prompt for input, or allow the user to verify or cancel an action.

Whilst these specific cases are also valid and true on the web, we also see a wide variety of other custom uses. For example, a dialog itself might contain an entire full page-like experience, a video, image gallery, navigation or settings.

There are various ways to visually present a dialog. The version here is the "lightbox" version. A dialog that visibly dims out the rest of the page while the dialog is open.

TIP: On a small screen, a lightbox dialog is usually replaced with a fullscreen or drawer dialog.

Updated: October 21st, 2020 (renamed from "Dialog")

Working Examples

Experience the pattern in action on our eBay MIND patterns examples website.

View the required markup structure by viewing the bones GitHub project.

Terminology

  • lightbox-dialog: the pattern as a whole, comprised of the following sub parts

  • parent window: the page containing the button that opens the dialog

  • dialog button: the button that opens the child window

  • child window: the overlay containing the dialog content

  • title: the title of the child window

  • modality: modal or non-modal, dialogs with a mask are always modal

  • mask: CSS effect that visibly dims out the parent window content

  • dismiss button: button that dismisses and hides the lightbox

Best Practices

A lightbox-dialog is typically opened in one of two ways:

  • Click-activated: explicitly opened and closed by clicking the button

  • System-activated: automatically opened by the system/application on page load or at some other arbitrary time

Opening modal dialogs that are not requested by user (i.e. system-activated) are a violation of WCAG Guideline 3.2.5 (Level AAA) and therefore should be reserved for exceptional circumstances only.

Lightbox dialog must not be opened on hover or focus of button.

Lightbox dialog must be opened on click-event of button or via an application event.

Lightbox dialog must contain a dismiss button.

Lightbox dialog must contain at least one interactive element (this can be the dismiss button).

Lightbox dialog must be clearly labelled by an onscreen heading/title

Lightbox dialog must start heading hierarchy at level 2.

Interaction Design

This section provides interaction design for keyboard, screen reader and pointing devices.

Keyboard

If dialog button has focus, ENTER or SPACE key must open lightbox dialog.

When lightbox dialog opens, focus must move to the dismiss button.

Lightbox dialog must confine TAB and SHIFT-TAB to focusable elements of child window.

Pressing ESC key must close lightbox dialog (unless the currently focussed element has existing ESC key behaviour).

Dialog button must receive focus when lightbox dialog is closed.

Screen Reader

Dialog button must not announce 'has popup'. A dialog is not considered a popup (i.e aria-haspopup="true" is not valid in this context).

Focussed element should be announced when child window opens (via focus management).

Title must be announced when lightbox dialog opens.

Virtual cursor must be confined within child window.

Assistive technology might announce "Entering/leaving dialog" (or words to those affect) when focus enters or leaves the lightbox dialog respectively.

Pointer

Clicking dialog button must open lightbox dialog.

Clicking mask can dismiss lightbox dialog.

Developer Guide

HTML does provide a <dialog> tag, but at the time of writing this tag does not have good cross browser support.

Content (HTML)

Button

We are going to add a button element that opens the lightbox dialog when clicked.

<button class="dialog-button" data-dialog="my-dialog" type="button">Open Dialog</button>

We have added a custom data attribute called data-dialog. The value of this attribute references the ID of the dialog element and will be used by JavaScript (more on that later).

Dialog Role

The root element of the lightbox dialog must have a role of "dialog".

<div id="my-dialog" role="dialog">
</div>

Hidden State

The lightbox dialog must have a property of hidden to prevent it from being displayed on page load.

<div id="my-dialog" role="dialog" hidden>
</div>

To display the dialog, simply remove this property. This property can be used as a hook for CSS transitions.

Child Window

The child window element contains the actual dialog content.

<div id="my-dialog" role="dialog" hidden>
<div class="dialog__window">
<!-- dialog content goes here -->
</div>
</div>

Header and Body

Next we create header and main structure, mimicking the structure of the main parent window.

<div id="my-dialog" role="dialog" hidden>
<div class="dialog__window">
<div class="dialog__header">
<!-- dialog header content goes here -->
</div>
<div class="dialog__main">
<!-- dialog main content goes here -->
</div>
</div>
</div>

Title

Every dialog must be labelled. We can use aria-labelledby to point to a suitable labelling element inside of our dialog.

<div id="my-dialog" role="dialog" aria-labelledby="dialog-title" hidden>
<div class="dialog__window">
<div class="dialog__header">
<h2 id="dialog-title">Dialog Example</h2>
</div>
<div class="dialog__main">
<!-- dialog main content goes here -->
</div>
</div>
</div>

Alternatively, if no suitable heading or labelling element exists, we can specify the label in an aria-label property instead:

<div id="my-dialog" role="dialog" aria-label="Dialog Example" hidden>

Close Button

Every dialog requires some form of close/dismiss button. For a lightbox dialog, this button should be placed in the header.

<div id="my-dialog" role="dialog" aria-labelledby="dialog-title" hidden>
<div class="dialog__window">
<div class="dialog__header">
<button aria-label="Close Dialog">
<svg aria-hidden="true" focusable="false" height="16" width="16">
<use xlink:href="#icon-close"></use>
</svg>
</button>
<h2 id="dialog-title">Dialog Example</h2>
</div>
<div class="dialog__main">
<!-- dialog contents go here -->
</div>
</div>
</div>

Presentation (CSS)

There are many different ways to visually, and uniquely, style a lightbox dialog. The most important aspects for accessibility are the mask and the hidden state.

Dialog Mask

The mask creates a visual distinction between the overlay content in the foreground, and the page content in the background. It also prevents pointing devices from interacting with the page content behind the window.

.lightbox-dialog {
background-color: rgba(51,51,51,0.7);
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
}

The mask does not prevent keyboard or screen reader from accessing the page content behind the window - we will need JavaScript for that.

Behaviour (JS)

Hide and Show

A dialog is made visible simply by toggling its hidden attribute.

function onDialogButtonClick() {
dialogEl.hidden = false;
}

CSS animation of the dialog and mask is outside the scope of this guide.

Modality for Keyboard

A lightbox dialog is always modal. A modal dialog must prevent keyboard focus moving from the child window to the parent window. In addition, keyboard focus must wrap from the last interactive element in the child window back to the first, and vice versa.

There are several different JavaScript techniques to achieve such behaviour. We provide an example JavaScript module, called makeup-keyboard-trap.

Modality for Screen Reader

While in an open state, any content that is not inside of the child window must be hidden from screen reader users. This can be achieved by applying aria-hidden="true" to any non direct-ancestor nodes in the DOM.

Remember to unhide these elements when the lightbox dialog is closed, otherwise the main page will be completely hidden & inaccessible to screen readers.

Again, we provide an example module, called makeup-screenreader-trap, to assist with this behaviour.

Prevent Page Scroll

It is desirable to prevent the background page from scrolling when interacting with the foreground dialog content. This can be achieved with a class that applies overflow:hidden to the html body.

body.has-modal-dialog {
overflow: hidden;
}

The presence of this class would be toggled by our JavaScript on dialog hide/show event.

ESC Key

Pressing the ESC key while focus is on the dismiss button, or any other interactive element that does not already have ESC key behaviour, should close the dialog.

windowEl.addEventListener('keydown', function(e) {
if (e.keyCode === 27) {
// hide the dialog
}
});

Any nested widgets should take care to prevent ESC event from bubbling.

Returning Focus

When the lightbox dialog is closed, keyboard focus should return to the dialog button in the main page.

dialogWidget.addEventListener('dialog-close', function () {
dialogButtonEl.focus();
});

NOTE: dialog-close is a custom event fired by the lightbox dialog widget.

JavaScript Modules

We have some experimental JavaScript modules that may assist you with creation of an accessible dialog widget:

ARIA Reference

This section gives an overview of ARIA usage within the context of this pattern.

role=dialog

Informs the assistive technology that the user is inside of a dialog.

aria-labelledby

Informs the assistive technology of the onscreen text used to label the dialog.

aria-label

Sets an explicit label value for the dialog, overriding any onscreen labelling text.

aria-hidden

While the dialog is in an open state, aria-hidden is used to hide all non-dialog elements from assistive technology.