ARIA Popup

Demo

Activate one of the buttons labeled "Confirm choice" or "Confirm a different choice" below to open the demo.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio eius quos provident pariatur incidunt, odio tenetur. Nam amet minus, ullam accusamus libero error ducimus sint pariatur delectus ut eligendi sapiente.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio eius quos provident pariatur incidunt, odio tenetur. Nam amet minus, ullam accusamus libero error ducimus sint pariatur delectus ut eligendi sapiente.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio eius quos provident pariatur incidunt, odio tenetur. Nam amet minus, ullam accusamus libero error ducimus sint pariatur delectus ut eligendi sapiente.

				
					
<body>
    <!--
        Our custom dialog component.
        - role="dialog" provides a role
        - aria-labelledby="dialog-name" provides an accessible name to this 
            component. It takes an id value, and then gives this component an
            accessible name based on the text content of the element with the 
            id value.
    -->
    <div role="dialog" id="example-dialog" class="dialog" aria-labelledby="dialog-name" hidden>
        <div class="header">
            <h1 id="dialog-name">Confirm Your Choice</h1>
            <button type="submit" class="close" data-value="cancel" data-first>
                <!-- We hide this symbol from assistive technology (AT) using
                     aria-hidden. We do this because the symbol may be read by
                     AT, but we don't want it to be part of the accessible name.
                -->
                <span aria-hidden="true">×</span>
                <!-- We add a visually hidden span element with text content 
                     that describes the purpose of the component.
                -->
                <span class="sr-only">Close</span>
            </button>
        </div>
        <div class="content">
            <p>
                Are you sure you want to do that?
            </p>
        </div>
        <div class="choices">
            <button type="submit" data-value="yes">Yes</button>
            <button type="submit" data-value="no">No</button>
            <button type="submit" data-value="cancel" data-last>Cancel</button>
        </div>
    </div>
    <!-- we use this .backdrop element to simulate the native <dialog> element's
         ::backdrop pseudo-element.    
    -->
    <div class="backdrop"></div>
    <main>
        <h1>ARIA Popup Demo</h1>
        <p>Activate one of the buttons labeled "Confirm choice" or "Confirm a different choice" below to open the demo.</p>
        <button data-dialog-opener>Confirm choice</button>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio eius quos provident pariatur incidunt, odio tenetur. Nam amet minus, ullam accusamus libero error ducimus sint pariatur delectus ut eligendi sapiente.</p>
        <button>This does nothing</button>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio eius quos provident pariatur incidunt, odio tenetur. Nam amet minus, ullam accusamus libero error ducimus sint pariatur delectus ut eligendi sapiente.</p>
        <button data-dialog-opener>Confirm a different choice</button>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio eius quos provident pariatur incidunt, odio tenetur. Nam amet minus, ullam accusamus libero error ducimus sint pariatur delectus ut eligendi sapiente.</p>
        <button>This does nothing</button>
    </main>
				
			
				
					
/** 
 * This declaration copies the default User Agent stylings for native 
 * <dialog> elements, including the modal verion since this demo uses
 * a modal dialog component.
 */
.dialog {
    /* default UA stylings for native <dialog> elements*/
    inset-inline-start: 0px;
    inset-inline-end: 0px;
    width: fit-content;
    height: fit-content;
    background-color: canvas;
    color: canvastext;
    margin: auto;
    border-width: initial;
    border-style: solid;
    border-color: initial;
    border-image: initial;

    /* default UA stylings for modal, native <dialog> elements*/
    position: fixed;
    inset-block-start: 0px;
    inset-block-end: 0px;
    max-width: calc((100% - 6px) - 2em);
    max-height: calc((100% - 6px) - 2em);
    user-select: text;
    visibility: visible;
    overflow: auto;
}

/* custom styling for our dialog component */
.dialog {
    border-radius: 0.35rem;
    border: none;
    box-shadow: 0px 3px 5px -3px black;
    padding: 0;
    /* z-index ensures that this component is not obscured by our .backdrop element. */
    z-index: 1;
}

/**
 * We are recreating the ::backdrop pseudo-element that comes naturally with 
 * the native <dialog> element. This .backdrop is placed directly after the 
 * .dialog element. This allows us to take advantage of the + operator and 
 * style the .backdrop element when the .dialog element is not hidden. 
 */
.dialog:not([hidden]) + .backdrop {
    content: "";
    /* positioning */
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    /* visually styling */
    background-color: black;
    opacity: 0.7;
}

/* ------------------------------------------------------------------------- */
/* Visual Stylings */
/* The CSS below is not particularly imporant. */
.dialog > * {
    padding: 0 1rem;
}

.dialog > *:last-child {
    padding-bottom: 1rem;
}

.header {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.5rem;
    border-bottom: 1px solid #777;
}

.header button {
    font-size: 1.2rem;
}

.header > h1 {
    padding: 0.25rem 1rem;
}

.content {
    text-align: center;
}

.choices {
    display: flex;
    justify-content: space-evenly;
}

.dialog h1 {
    font-size: 1.25rem;
    font-weight: 600;
    margin: 0;
}

/* visually hides content */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}
				
			
				
					
let exampleDialog = document.getElementById('example-dialog');

// we have identified the first and last focusable elements in the
// custom dialog component using html dataset attributes
let firstFocusableElement = exampleDialog.querySelector('[data-first]');
let lastFocusableElement = exampleDialog.querySelector('[data-last]');

firstFocusableElement.addEventListener('keydown', (e) => {
    // if the user is not moving focus backwards from the first
    // focusable element, then we do nothing
    if (!e.getModifierState("Shift") || e.key !== 'Tab') return;
    // otherwise, we set focus to the last focusable element
    lastFocusableElement.focus();
    // we prevent the default to ensure that focus is not shifted again
    e.preventDefault();
});

lastFocusableElement.addEventListener('keydown', (e) => {
    // if user is not moving focus forwards, or not moving focus at all,
    // then we do nothing
    if (e.getModifierState("Shift") || e.key !== 'Tab') return;
    // otherwise, we set focus to the first focusable element
    firstFocusableElement.focus();
    // we prevent the default to ensure that focus is not shifted again
    e.preventDefault();
});

// we keep track of the element that opened the dialog with the
// lastFocused variable.
let lastFocused;
const closeDialog = (dialog, returnValue) => {
    // set the return value using dataset in an attempt to 
    // copy the native <dialog> element's returnValue property
    dialog.dataset.returnValue = returnValue;
    // when we close the example dialog, we set focus back onto
    // the element that opened the dialog
    lastFocused.focus();
    // the hidden attribute is roughly equivalent to the CSS
    // property "display: none". An element with hidden=true,
    // it will be hidden from assistive technology as well as
    // visually.
    dialog.hidden = true;
}

// add eventlisteners for each button in our custom dialog component
exampleDialog.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('click', (e) => {
        closeDialog(exampleDialog, btn.dataset.value);
    });
});

// this section of code opens the dialog
// we find all the elements with the data-dialog-opener attribute
// and then ensure that they open the dialog when it opens.
let openers = document.querySelectorAll('[data-dialog-opener]');
openers.forEach(opener => {
    opener.addEventListener('click', (e) => {
        // we save the last focused element. This allows us to
        // return focus to this element when the dialog is closed
        lastFocused = opener;
        // we unhide the dialog
        exampleDialog.hidden = false;
        // we focus the first focusable element
        firstFocusableElement.focus();
    });
});

// the native <dialog> element allows users to close the <dialog>
// by pressing the 'Escape' key. This copies that functionality.
exampleDialog.addEventListener('keydown', (e) => {
    // we only execute if the user pressed the escape key
    if (e.key === 'Escape') closeDialog(exampleDialog, 'cancel');
});
				
			

Why use a popup/dialog?

Dialog components in web development enhance the user experience by providing a focused and interactive space for specific interactions. Dialogs serve as modal overlays, allowing the isolation of tasks such as user authentication, form submissions, or content previews without disrupting the overall user flow. They are particularly valuable for displaying alerts, notifications, and confirmation prompts, guiding users through decision points with clarity. Dialogs also facilitate the presentation of media content, interactive widgets, and help information, contributing to a consistent and user-friendly interface. Moreover, their responsive design capabilities ensure adaptability across various devices. With accessibility features, dialog components cater to a diverse user base, ensuring an inclusive and efficient interaction model in web applications and sites.

What are some accessibility concerns?

  1. Keyboard Focus and Navigation:

    • Concern: Ensure that keyboard focus is managed appropriately when a dialog opens. Users should be able to navigate through the interactive elements within the dialog using the keyboard.

    • Mitigation: Set focus to the first focusable element within the dialog when it opens. Implement keyboard navigation and ensure that users can easily navigate within and close the dialog using keyboard inputs.
  2. Screen Reader Compatibility:

    • Concern: Screen reader users may not be notified when a dialog opens or may have difficulty understanding the content within the dialog.
    • Mitigation: Use ARIA (Accessible Rich Internet Applications) roles and attributes to announce the dialog’s presence and purpose to screen readers. Ensure that relevant information within the dialog is properly labeled and conveyed.
  3. Focus Trapping:

    • Concern: Users may unintentionally navigate outside the dialog using keyboard inputs, especially if the background content is still accessible.
    • Mitigation: Implement focus trapping to restrict keyboard focus within the dialog when it is open. This prevents users from tabbing out of the dialog and ensures a logical focus order.
  4. Clear and Concise Content:

    • Concern: Ensure that the content within the dialog is clear, concise, and easily understandable for users with various disabilities.
    • Mitigation: Provide descriptive and well-structured content within the dialog. Use plain language, and consider the needs of users with cognitive disabilities when presenting information.
  5. Close Button Accessibility:

    • Concern: Users need a clear and accessible way to close the dialog.
    • Mitigation: Include a visible and accessible close button within the dialog. Ensure that users can close the dialog using keyboard controls, typically the Escape key.
Tested using
Chrome
with NVDA
Firefox
with NVDA
  • W3C SVG
    1.3.1
    Info and Relationships ( level A ) :
    Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
  • W3C SVG
    2.4.3
    Focus Order ( level A ) :
    If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.
  • W3C SVG
    4.1.3
    Status Messages ( level AA ) :
    In content implemented using markup languages, status messages can be programmatically determined through role or properties such that they can be presented to the user by assistive technologies without receiving focus.