Make WordPress Core

Changeset 59224


Ignore:
Timestamp:
10/13/2024 07:07:06 PM (2 years 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.