Crocoblock / suggestions

The suggestions for CrocoBlock project
196 stars 79 forks source link

Jet Popups are not compliant with WCAG accessibility standards #7303

Open fafchook opened 10 months ago

fafchook commented 10 months ago

The popups are not complaint with modern accessibility needs. this includes aria labels and role attribute, tabindex and mainly the need to focus the popup and the close button for keyboard users. For a good reference have a look at the improved Elementor accessible popups.

The popup should be fully controlled via the keyboard using the tab key (and up and down keys). The focus should be set by default to the close button. and on close the focus should return to the previous clicked element. The popup should be properly coded for screen readers.

Thanks

fafchook commented 10 months ago

If this helps this is a code I am using in a project. it's not perfect but it works for me. this code is for a popup that is triggered from a calendar listing. the problem is that the calendar is also not wcag compliant, and needs work as well:

jQuery(document).ready(function($) {
    var previousFocusElement;

    $('.jet-popup-target').on('click', function() {
        // Store the reference to the previously focused element
        previousFocusElement = document.activeElement;

        // Open the popup
        var popup = $('.jet-popup');
        popup.attr('tabindex', 0).attr('role', 'dialog').attr('aria-modal', 'true').focus();

        // Set tabindex and role for the close button
        var closeButton = $('.jet-popup .jet-popup__close-button');
        closeButton.attr('tabindex', 0).attr('role', 'button').attr('aria-label', 'close popup');

        // Handle keyboard navigation within the popup
        popup.on('keydown', function(event) {
            var focusableElements = popup.find('button, [href], .jet-listing-dynamic-link__link, [tabindex="0"], [role="button"], .elementor-swiper-button').not('.elementor-hidden');
            var firstElement = focusableElements.first();
            var lastElement = focusableElements.last();

            if (event.key === 'Tab') {
                // Handle tab key
                if (event.shiftKey) {
                    // Shift + Tab
                    if (document.activeElement === firstElement[0]) {
                        event.preventDefault();
                        lastElement.focus();
                    }
                } else {
                    // Tab
                    if (document.activeElement === lastElement[0]) {
                        event.preventDefault();
                        firstElement.focus();
                    }
                }
            }
        });

        // Set initial focus on the close button after a short delay
        setTimeout(function() {
            closeButton.focus();
        }, 1000); // You can adjust the delay as needed
    });

    $('.jet-popup__close-button').on('keydown', function(event) {
        // Check if the pressed key is Enter (key code 13)
        if (event.key === 'Enter') {
            // Trigger a click event on the element
            $(this).click();
        }
    });

    // Add an event listener to handle the popup closing
    $('.jet-popup').on('transitionend', function() {
        // Restore focus to the previously focused element
        if (previousFocusElement) {
            previousFocusElement.focus();
        }
    });
});
fafchook commented 10 months ago

Hey guys, this is the updated code that I believe users will be happy if you add to the plugin. I have tested and implanted it in a few websites and it works greate!

<script>
    jQuery(document).ready(function($) {
    var previousFocusElement;
    jQuery('.jet-popup-target').attr({'tabindex': '0', 'role': 'button'});
    $( window ).on('jet-popup/show-event/after-show', function() {
        // Store the reference to the previously focused element
        previousFocusElement = document.activeElement;

        // Add role and aria-modal to the popup
        var popup = $('.jet-popup');
        popup.attr({'role': 'dialog', 'aria-modal': 'true'});
                // Find the first heading within the popup element
                var firstHeading = popup.find(':header:first');
                // Check if the firstHeading exists
                if (firstHeading.length) {
                    // Check if the firstHeading already has an id
                    if (!firstHeading.attr('id')) {
                        // If it doesn't have an id, generate and set one
                        var generatedId = 'modalHeading_' + Math.random().toString(36).substring(2, 15);
                        firstHeading.attr('id', generatedId);
                    }

                    // Add aria-labelledby attribute to the popup element with the value of the firstHeading's id
                    popup.attr('aria-labelledby', firstHeading.attr('id'));
                }
        // Set tabindex and role for the close button & Set focus on the close button
        var closeButton = $('.jet-popup .jet-popup__close-button');
        closeButton.attr({'tabindex': 0, 'role': 'button', 'aria-label': 'close popup'}).focus();

        // Handle keyboard navigation within the popup
        popup.on('keydown', function(event) {
            var focusableElements = popup.find('button, [href], .jet-listing-dynamic-link__link, [tabindex="0"], [role="button"]').not('.elementor-hidden');
            var firstElement = focusableElements.first();
            var lastElement = focusableElements.last();

            if (event.key === 'Tab') {
                // Handle tab key
                if (event.shiftKey) {
                    // Shift + Tab
                    if (document.activeElement === firstElement[0]) {
                        event.preventDefault();
                        lastElement.focus();
                    }
                } else {
                    // Tab
                    if (document.activeElement === lastElement[0]) {
                        event.preventDefault();
                        firstElement.focus();
                    }
                }
            }
        });

    });

    // Make the popup trigger and popup close button respond to the enter key like a click 
    $('.jet-popup-target, .jet-popup__close-button').on('keydown', function(event) {
        // Check if the pressed key is Enter (key code 13)
        if (event.key === 'Enter') {
            // Trigger a click event on the element
            $(this).click();
        }
    });

    // Add an event listener to handle the popup closing
    $( window ).on('jet-popup/hide-event/after-hide', function() {
        // Restore focus to the previously focused element
        if (previousFocusElement) {
            previousFocusElement.focus();
        }
    });
});
</script>
simplicity17 commented 3 weeks ago

This is an issue for me as well. We had an accessibility audit completed on our website and this issue was mentioned as Critical because it severely hinders the user's ability to navigate the site. We would switch to Elementor popups, but a form embed we are using doesn't work correctly, so we need to use JetPopups.