Skip to content

[a11y] Ensure Modal dialog is fully accessible #543

@codebymikey

Description

@codebymikey

Was planning to move to this library as part of Drupal's suggestion for having good a11y support, but ran into this issue whilst testing.

The specification and examples for creating an accessible modal dialog is defined here: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/#ex_label

The main issues highlighted so far:

  • When Tab is pressed, and the focus is on the last focusable element within the dialog, it should move focus back to the first focusable element in the dialog, rather than to an element that's below the dialog.
  • Necessary aria- attributes are missing from the modal, e.g. role="dialog", aria-modal="true" etc.
  • There should be visual feedback when the checkboxes and other elements are currently in focus, e.g. https://getbootstrap.com/docs/4.0/components/modal/#modal-components

Steps to reproduce

  • Create a modal dialog for Klaro, and attempt to navigate entirely through the keyboard or a screenreader.

Current behavior

  • Pressing tab will cause the user to focus out of the modal, which isn't accessible.
  • Certain ARIA attributes are missing.

Expected behavior

Tabbing should be locked to just the focusable elements within the modal, stopping the user from accidentally tabbing to an element beneath the dialog.


The resource linked to above should provide resources as for how to best implement this feature.
However this piece of snippet I wrote may also be used to provide the necessary keyboard support until this is supported natively.

NB: My implementation directly focuses on keyboard events whilst theirs handles all scenarios where focus is placed on a foreign element whilst the modal is open:

function keyboardHandler(event) {
    if (event.key === 'Tab' || event.keyCode === 9) {
        const container = document.querySelector('#klaro');
        if (!container) {
            return;
        }
        // Get all focusable elements inside the container
        const focusableElements = container.querySelectorAll('button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];

        // If Shift + Tab on the first focusable element, move focus to the last
        if (event.shiftKey && document.activeElement === firstElement) {
            event.preventDefault();
            lastElement.focus();
        }
        else if (!event.shiftKey && document.activeElement === lastElement) {
            // If we're Tabbing on the last focusable element, move focus back to the first
            event.preventDefault();
            firstElement.focus();
        }
    }
    else if (event.key === 'Escape' || event.keyCode === 27) {
        // Try clicking the close button.
        const closeButton = document.querySelector('#klaro button.hide');
        if (closeButton) {
            closeButton.click();
        }
    }
}
window.addEventListener('keydown', keyboardHandler);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions