Make WordPress Core

Changeset 59224


Ignore:
Timestamp:
10/13/2024 07:07:06 PM (8 weeks ago)
Author:
joedolson
Message:

Administration: A11y: Fix accordion accessibility.

Change accordions in the customizer and the navigation menus to make proper usage of accordion markup patterns. This includes adding missing :focus states, using a button element to control tabbing and interaction, instead of the heading elements, and removing instructional text for screen reader users that was used to compensate for the incorrect markup pattern.

Props afercia, rishishah, kushang78, rcreators, krupajnanda, hmbashar, joedolson.
Fixes #42002.

Location:
trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/lib/accordion.js

    r50547 r59224  
    88 * <div class="accordion-container">
    99 *  <div class="accordion-section open">
    10  *      <h3 class="accordion-section-title"></h3>
    11  *      <div class="accordion-section-content">
     10 *      <h3 class="accordion-section-title"><button type="button" aria-expanded="true" aria-controls="target-1"></button></h3>
     11 *      <div class="accordion-section-content" id="target">
    1212 *      </div>
    1313 *  </div>
    1414 *  <div class="accordion-section">
    15  *      <h3 class="accordion-section-title"></h3>
    16  *      <div class="accordion-section-content">
     15 *      <h3 class="accordion-section-title"><button type="button" aria-expanded="false" aria-controls="target-2"></button></h3>
     16 *      <div class="accordion-section-content" id="target-2">
    1717 *      </div>
    1818 *  </div>
    1919 *  <div class="accordion-section">
    20  *      <h3 class="accordion-section-title"></h3>
    21  *      <div class="accordion-section-content">
     20 *      <h3 class="accordion-section-title"><button type="button" aria-expanded="false" aria-controls="target-3"></button></h3>
     21 *      <div class="accordion-section-content" id="target-3">
    2222 *      </div>
    2323 *  </div>
     
    3535
    3636        // Expand/Collapse accordion sections on click.
    37         $( '.accordion-container' ).on( 'click keydown', '.accordion-section-title', function( e ) {
    38             if ( e.type === 'keydown' && 13 !== e.which ) { // "Return" key.
    39                 return;
    40             }
    41 
    42             e.preventDefault(); // Keep this AFTER the key filter above.
    43 
     37        $( '.accordion-container' ).on( 'click', '.accordion-section-title button', function() {
    4438            accordionSwitch( $( this ) );
    4539        });
     
    5549    function accordionSwitch ( el ) {
    5650        var section = el.closest( '.accordion-section' ),
    57             sectionToggleControl = section.find( '[aria-expanded]' ).first(),
    5851            container = section.closest( '.accordion-container' ),
    5952            siblings = container.find( '.open' ),
     
    8780
    8881        // If there's an element with an aria-expanded attribute, assume it's a toggle control and toggle the aria-expanded value.
    89         if ( sectionToggleControl ) {
    90             sectionToggleControl.attr( 'aria-expanded', String( sectionToggleControl.attr( 'aria-expanded' ) === 'false' ) );
     82        if ( el ) {
     83            el.attr( 'aria-expanded', String( el.attr( 'aria-expanded' ) === 'false' ) );
    9184        }
    9285    }
  • trunk/src/js/_enqueues/wp/customize/controls.js

    r59197 r59224  
    15311531
    15321532            // Expand/Collapse accordion sections on click.
    1533             section.container.find( '.accordion-section-title, .customize-section-back' ).on( 'click keydown', function( event ) {
     1533            section.container.find( '.accordion-section-title button, .customize-section-back' ).on( 'click keydown', function( event ) {
    15341534                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    15351535                    return;
     
    16061606                overlay = section.headContainer.closest( '.wp-full-overlay' ),
    16071607                backBtn = content.find( '.customize-section-back' ),
    1608                 sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(),
     1608                sectionTitle = section.headContainer.find( '.accordion-section-title button' ).first(),
    16091609                expand, panel;
    16101610
     
    16161616                    expand = function() {
    16171617                        section._animateChangeExpanded( function() {
    1618                             sectionTitle.attr( 'tabindex', '-1' );
    1619                             backBtn.attr( 'tabindex', '0' );
    1620 
    16211618                            backBtn.trigger( 'focus' );
    16221619                            content.css( 'top', '' );
     
    16641661                }
    16651662                section._animateChangeExpanded( function() {
    1666                     backBtn.attr( 'tabindex', '-1' );
    1667                     sectionTitle.attr( 'tabindex', '0' );
    16681663
    16691664                    sectionTitle.trigger( 'focus' );
     
    27002695                content = section.contentContainer,
    27012696                backBtn = content.find( '.customize-section-back' ),
    2702                 sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(),
     2697                sectionTitle = section.headContainer.find( '.accordion-section-title button' ).first(),
    27032698                body = $( document.body ),
    27042699                expand, panel;
     
    27202715                    expand = function() {
    27212716                        section._animateChangeExpanded( function() {
    2722                             sectionTitle.attr( 'tabindex', '-1' );
    2723                             backBtn.attr( 'tabindex', '0' );
    2724 
    27252717                            backBtn.trigger( 'focus' );
    27262718                            content.css( 'top', '' );
     
    27532745                }
    27542746                section._animateChangeExpanded( function() {
    2755                     backBtn.attr( 'tabindex', '-1' );
    2756                     sectionTitle.attr( 'tabindex', '0' );
    27572747
    27582748                    sectionTitle.trigger( 'focus' );
     
    28442834
    28452835            // Expand/Collapse accordion sections on click.
    2846             panel.headContainer.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
     2836            panel.headContainer.find( '.accordion-section-title button' ).on( 'click keydown', function( event ) {
    28472837                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    28482838                    return;
     
    29482938                overlay = accordionSection.closest( '.wp-full-overlay' ),
    29492939                container = accordionSection.closest( '.wp-full-overlay-sidebar-content' ),
    2950                 topPanel = panel.headContainer.find( '.accordion-section-title' ),
     2940                topPanel = panel.headContainer.find( '.accordion-section-title button' ),
    29512941                backBtn = accordionSection.find( '.customize-panel-back' ),
    29522942                childSections = panel.sections(),
     
    29752965                } else {
    29762966                    panel._animateChangeExpanded( function() {
    2977                         topPanel.attr( 'tabindex', '-1' );
    2978                         backBtn.attr( 'tabindex', '0' );
    2979 
    29802967                        backBtn.trigger( 'focus' );
    29812968                        accordionSection.css( 'top', '' );
     
    29972984                if ( ! skipTransition ) {
    29982985                    panel._animateChangeExpanded( function() {
    2999                         topPanel.attr( 'tabindex', '0' );
    3000                         backBtn.attr( 'tabindex', '-1' );
    30012986
    30022987                        topPanel.focus();
  • trunk/src/js/_enqueues/wp/customize/nav-menus.js

    r58306 r59224  
    11081108                $title;
    11091109
    1110             $title = section.container.find( '.accordion-section-title:first' );
     1110            $title = section.container.find( '.accordion-section-title button:first' );
    11111111            $title.find( '.menu-in-location' ).remove();
    11121112            _.each( themeLocationSlugs, function( themeLocationSlug ) {
  • trunk/src/wp-admin/css/common.css

    r59046 r59224  
    24322432/* Back-compat for nav-menus screen */
    24332433.nav-menus-php .metabox-holder h3 {
     2434    padding: 0;
     2435}
     2436.nav-menus-php .metabox-holder .accordion-section-title button.accordion-trigger {
     2437    background: inherit;
     2438    color: #1d2327;
     2439    display: block;
     2440    position: relative;
     2441    text-align: left;
     2442    width: 100%;
     2443    outline: none;
     2444    border: 0;
    24342445    padding: 10px 10px 11px 14px;
    24352446    line-height: 1.5;
     2447    cursor: pointer;
     2448}
     2449.nav-menus-php .metabox-holder .accordion-section-title button.accordion-trigger:focus {
     2450    box-shadow: 0 0 0 2px #2271b1;
     2451    outline: 2px solid transparent;
     2452}
     2453.nav-menus-php .metabox-holder .accordion-section-title span.dashicons.dashicons-arrow-down {
     2454    position: absolute;
     2455    right: 10px;
     2456    left: auto;
     2457    color: #787c82;
     2458    border-radius: 50px;
     2459}
     2460.nav-menus-php .metabox-holder .accordion-section-title:hover span.dashicons.dashicons-arrow-down {
     2461    color: #1d2327;
     2462}
     2463.nav-menus-php .metabox-holder .accordion-section-title span.dashicons.dashicons-arrow-down::before {
     2464    position: relative;
     2465    left: -1px
     2466}
     2467
     2468.nav-menus-php .metabox-holder .accordion-section.open .accordion-section-title span.dashicons.dashicons-arrow-down {
     2469    transform: rotate(180deg);
    24362470}
    24372471
     
    35233557/* @todo: can we use a common class for these? */
    35243558.nav-menus-php .item-edit:before,
    3525 .widget-top .widget-action .toggle-indicator:before,
    3526 .control-section .accordion-section-title:after,
    3527 .accordion-section-title:after {
     3559.wp-customizer .control-section .accordion-section-title:after,
     3560.wp-customizer .accordion-section-title:after,
     3561.widget-top .widget-action .toggle-indicator:before {
    35283562    content: "\f140";
    35293563    font: normal 20px/1 dashicons;
     
    35433577.postbox .handlediv.button-link,
    35443578.item-edit,
    3545 .toggle-indicator,
    3546 .accordion-section-title:after {
     3579.toggle-indicator {
    35473580    color: #787c82;
    35483581}
     
    35603593.item-edit:hover,
    35613594.item-edit:focus,
    3562 .sidebar-name:hover .toggle-indicator,
    3563 .accordion-section-title:hover:after {
     3595.sidebar-name:hover .toggle-indicator {
    35643596    color: #1d2327;
    35653597    /* Only visible in Windows High Contrast mode */
     
    35733605}
    35743606
    3575 .control-section .accordion-section-title:after,
    3576 .accordion-section-title:after {
    3577     float: right;
    3578     right: 20px;
    3579     top: -2px;
    3580 }
    3581 
    3582 .control-section.open .accordion-section-title:after,
    35833607#customize-info.open .accordion-section-title:after,
    35843608.nav-menus-php .menu-item-edit-active .item-edit:before,
     
    39703994    }
    39713995
     3996    .nav-menus-php .metabox-holder h3 {
     3997        padding: 0;
     3998    }
     3999
    39724000    .postbox .handlediv {
    39734001        margin-top: 3px;
  • trunk/src/wp-admin/css/customize-controls.css

    r59078 r59224  
    554554}
    555555
     556.accordion-section-title:has(button.accordion-trigger) {
     557    padding: 0;
     558}
     559
     560.accordion-section-title button.accordion-trigger {
     561    all: unset;
     562    width: 100%;
     563    height: 100%;
     564    padding: 10px 10px 11px 14px;
     565    display: flex;
     566    align-items: center;
     567}
     568
     569.accordion-section-title button.accordion-trigger:has(.menu-in-location) {
     570    display: block;
     571}
     572
    556573@media (prefers-reduced-motion: reduce) {
    557574    #customize-theme-controls .accordion-section-title,
     
    580597
    581598#customize-controls .control-section:hover > .accordion-section-title,
    582 #customize-controls .control-section .accordion-section-title:hover,
     599#customize-controls .control-section .accordion-section-title button:hover,
    583600#customize-controls .control-section.open .accordion-section-title,
    584 #customize-controls .control-section .accordion-section-title:focus {
     601#customize-controls .control-section .accordion-section-title button:focus {
    585602    color: #2271b1;
    586603    background: #f6f7f7;
  • trunk/src/wp-admin/css/customize-nav-menus.css

    r58146 r59224  
    543543}
    544544
    545 #available-menu-items .accordion-section-title button {
    546     display: block;
     545#available-menu-items .accordion-section-title button .toggle-indicator {
     546    display: flex;
     547    align-items: center;
    547548    width: 28px;
    548549    height: 35px;
  • trunk/src/wp-admin/includes/template.php

    r59114 r59224  
    15671567                    $hidden_class = in_array( $box['id'], $hidden, true ) ? 'hide-if-js' : '';
    15681568
    1569                     $open_class = '';
     1569                    $open_class    = '';
     1570                    $aria_expanded = 'false';
    15701571                    if ( ! $first_open && empty( $hidden_class ) ) {
    1571                         $first_open = true;
    1572                         $open_class = 'open';
     1572                        $first_open    = true;
     1573                        $open_class    = 'open';
     1574                        $aria_expanded = 'true';
    15731575                    }
    15741576                    ?>
    15751577                    <li class="control-section accordion-section <?php echo $hidden_class; ?> <?php echo $open_class; ?> <?php echo esc_attr( $box['id'] ); ?>" id="<?php echo esc_attr( $box['id'] ); ?>">
    1576                         <h3 class="accordion-section-title hndle" tabindex="0">
    1577                             <?php echo esc_html( $box['title'] ); ?>
    1578                             <span class="screen-reader-text">
    1579                                 <?php
    1580                                 /* translators: Hidden accessibility text. */
    1581                                 _e( 'Press return or enter to open this section' );
    1582                                 ?>
    1583                             </span>
     1578                        <h3 class="accordion-section-title hndle">
     1579                            <button type="button" class="accordion-trigger" aria-expanded="<?php echo $aria_expanded; ?>" aria-controls="<?php echo esc_attr( $box['id'] ); ?>-content">
     1580                                <span class="accordion-title">
     1581                                    <?php echo esc_html( $box['title'] ); ?>
     1582                                    <span class="dashicons dashicons-arrow-down" aria-hidden="true"></span>
     1583                                </span>
     1584                            </button>
    15841585                        </h3>
    1585                         <div class="accordion-section-content <?php postbox_classes( $box['id'], $page ); ?>">
     1586                        <div class="accordion-section-content <?php postbox_classes( $box['id'], $page ); ?>" id="<?php echo esc_attr( $box['id'] ); ?>-content">
    15861587                            <div class="inside">
    15871588                                <?php call_user_func( $box['callback'], $data_object, $box ); ?>
  • trunk/src/wp-includes/class-wp-customize-nav-menus.php

    r58306 r59224  
    12241224        <div id="<?php echo esc_attr( $id ); ?>" class="accordion-section">
    12251225            <h4 class="accordion-section-title" role="presentation">
    1226                 <?php echo esc_html( $available_item_type['title'] ); ?>
    1227                 <span class="spinner"></span>
    1228                 <span class="no-items"><?php _e( 'No items' ); ?></span>
    1229                 <button type="button" class="button-link" aria-expanded="false">
    1230                     <span class="screen-reader-text">
    1231                     <?php
    1232                         /* translators: %s: Title of a section with menu items. */
    1233                         printf( __( 'Toggle section: %s' ), esc_html( $available_item_type['title'] ) );
    1234                     ?>
    1235                         </span>
     1226                <button type="button" class="accordion-trigger" aria-expanded="false" aria-controls="<?php echo esc_attr( $id ); ?>-content">
     1227                    <?php echo esc_html( $available_item_type['title'] ); ?>
     1228                    <span class="spinner"></span>
     1229                    <span class="no-items"><?php _e( 'No items' ); ?></span>
    12361230                    <span class="toggle-indicator" aria-hidden="true"></span>
    12371231                </button>
    12381232            </h4>
    1239             <div class="accordion-section-content">
     1233            <div class="accordion-section-content" id="<?php echo esc_attr( $id ); ?>-content">
    12401234                <?php if ( 'post_type' === $available_item_type['type'] ) : ?>
    12411235                    <?php $post_type_obj = get_post_type_object( $available_item_type['object'] ); ?>
     
    12651259        <div id="new-custom-menu-item" class="accordion-section">
    12661260            <h4 class="accordion-section-title" role="presentation">
    1267                 <?php _e( 'Custom Links' ); ?>
    1268                 <button type="button" class="button-link" aria-expanded="false">
    1269                     <span class="screen-reader-text">
    1270                         <?php
    1271                         /* translators: Hidden accessibility text. */
    1272                         _e( 'Toggle section: Custom Links' );
    1273                         ?>
    1274                     </span>
     1261                <button type="button" class="accordion-trigger" aria-expanded="false" aria-controls="new-custom-menu-item-content">
     1262                    <?php _e( 'Custom Links' ); ?>
    12751263                    <span class="toggle-indicator" aria-hidden="true"></span>
    12761264                </button>
    12771265            </h4>
    1278             <div class="accordion-section-content customlinkdiv">
     1266            <div class="accordion-section-content customlinkdiv" id="new-custom-menu-item-content">
    12791267                <input type="hidden" value="custom" id="custom-menu-item-type" name="menu-item[-1][menu-item-type]" />
    12801268                <p id="menu-item-url-wrap" class="wp-clearfix">
  • trunk/src/wp-includes/class-wp-customize-panel.php

    r56551 r59224  
    347347        ?>
    348348        <li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">
    349             <h3 class="accordion-section-title" tabindex="0">
    350                 {{ data.title }}
    351                 <span class="screen-reader-text">
    352                     <?php
    353                     /* translators: Hidden accessibility text. */
    354                     _e( 'Press return or enter to open this panel' );
    355                     ?>
    356                 </span>
     349            <h3 class="accordion-section-title">
     350                <button type="button" class="accordion-trigger" aria-expanded="false" aria-controls="{{ data.id }}-content">
     351                    {{ data.title }}
     352                </button>
    357353            </h3>
    358             <ul class="accordion-sub-container control-panel-content"></ul>
     354            <ul class="accordion-sub-container control-panel-content" id="{{ data.id }}-content"></ul>
    359355        </li>
    360356        <?php
  • trunk/src/wp-includes/class-wp-customize-section.php

    r56551 r59224  
    356356        ?>
    357357        <li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">
    358             <h3 class="accordion-section-title" tabindex="0">
    359                 {{ data.title }}
    360                 <span class="screen-reader-text">
    361                     <?php
    362                     /* translators: Hidden accessibility text. */
    363                     _e( 'Press return or enter to open this section' );
    364                     ?>
    365                 </span>
     358            <h3 class="accordion-section-title">
     359                <button type="button" class="accordion-trigger" aria-expanded="false" aria-controls="{{ data.id }}-content">
     360                    {{ data.title }}
     361                </button>
    366362            </h3>
    367             <ul class="accordion-section-content">
     363            <ul class="accordion-section-content" id="{{ data.id }}-content">
    368364                <li class="customize-section-description-container section-meta <# if ( data.description_hidden ) { #>customize-info<# } #>">
    369365                    <div class="customize-section-title">
  • trunk/src/wp-includes/customize/class-wp-customize-themes-panel.php

    r55276 r59224  
    4545                }
    4646                ?>
    47 
    4847                <?php if ( current_user_can( 'switch_themes' ) ) : ?>
    4948                    <button type="button" class="button change-theme" aria-label="<?php esc_attr_e( 'Change theme' ); ?>"><?php _ex( 'Change', 'theme' ); ?></button>
  • trunk/tests/phpunit/tests/customize/nav-menus.php

    r56548 r59224  
    770770            foreach ( $post_types as $type ) {
    771771                $this->assertStringContainsString( 'available-menu-items-post_type-' . esc_attr( $type->name ), $template );
    772                 $this->assertMatchesRegularExpression( '#<h4 class="accordion-section-title".*>\s*' . esc_html( $type->labels->name ) . '#', $template );
     772                $this->assertMatchesRegularExpression( '#<h4 class="accordion-section-title".*>\s*<button type="button" class="accordion-trigger" aria-expanded="false" aria-controls=".*">\s*' . esc_html( $type->labels->name ) . '#', $template );
    773773                $this->assertStringContainsString( 'data-type="post_type"', $template );
    774774                $this->assertStringContainsString( 'data-object="' . esc_attr( $type->name ) . '"', $template );
     
    781781            foreach ( $taxonomies as $tax ) {
    782782                $this->assertStringContainsString( 'available-menu-items-taxonomy-' . esc_attr( $tax->name ), $template );
    783                 $this->assertMatchesRegularExpression( '#<h4 class="accordion-section-title".*>\s*' . esc_html( $tax->labels->name ) . '#', $template );
     783                $this->assertMatchesRegularExpression( '#<h4 class="accordion-section-title".*>\s*<button type="button" class="accordion-trigger" aria-expanded="false" aria-controls=".*">\s*' . esc_html( $tax->labels->name ) . '#', $template );
    784784                $this->assertStringContainsString( 'data-type="taxonomy"', $template );
    785785                $this->assertStringContainsString( 'data-object="' . esc_attr( $tax->name ) . '"', $template );
     
    789789
    790790        $this->assertStringContainsString( 'available-menu-items-custom_type', $template );
    791         $this->assertMatchesRegularExpression( '#<h4 class="accordion-section-title".*>\s*Custom#', $template );
     791        $this->assertMatchesRegularExpression( '#<h4 class="accordion-section-title".*>\s*<button type="button" class="accordion-trigger" aria-expanded="false" aria-controls=".*">\s*Custom#', $template );
    792792        $this->assertStringContainsString( 'data-type="custom_type"', $template );
    793793        $this->assertStringContainsString( 'data-object="custom_object"', $template );
Note: See TracChangeset for help on using the changeset viewer.