WordPress.org

Make WordPress Core

Changeset 48349


Ignore:
Timestamp:
07/06/2020 08:42:14 PM (3 months ago)
Author:
afercia
Message:

Accessibility: Widgets: Add theme support to make widgets output list of links wrapped within a <nav> element.

Widgets that output list of links can now be wrapped within <nav> elements to improve semantics and accessibility.

The <nav> elements are also native landmark regions, which helps assistive technology users to navigate through them. Themes can opt-in to this new behavior by declaring support for the new html5 feature navigation-widgets.

Props joedolson, simonjanin, audrasjb, afercia.
Fixes #48170.

Location:
trunk/src/wp-includes
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/nav-menu-template.php

    r48185 r48349  
    1616 * @since 3.0.0
    1717 * @since 4.7.0 Added the `item_spacing` argument.
     18 * @since 5.5.0 Added the `container_aria_label` argument.
    1819 *
    1920 * @param array $args {
    2021 *     Optional. Array of nav menu arguments.
    2122 *
    22  *     @type int|string|WP_Term $menu            Desired menu. Accepts a menu ID, slug, name, or object. Default empty.
    23  *     @type string             $menu_class      CSS class to use for the ul element which forms the menu. Default 'menu'.
    24  *     @type string             $menu_id         The ID that is applied to the ul element which forms the menu.
    25  *                                               Default is the menu slug, incremented.
    26  *     @type string             $container       Whether to wrap the ul, and what to wrap it with. Default 'div'.
    27  *     @type string             $container_class Class that is applied to the container. Default 'menu-{menu slug}-container'.
    28  *     @type string             $container_id    The ID that is applied to the container. Default empty.
    29  *     @type callable|bool      $fallback_cb     If the menu doesn't exist, a callback function will fire.
    30  *                                               Default is 'wp_page_menu'. Set to false for no fallback.
    31  *     @type string             $before          Text before the link markup. Default empty.
    32  *     @type string             $after           Text after the link markup. Default empty.
    33  *     @type string             $link_before     Text before the link text. Default empty.
    34  *     @type string             $link_after      Text after the link text. Default empty.
    35  *     @type bool               $echo            Whether to echo the menu or return it. Default true.
    36  *     @type int                $depth           How many levels of the hierarchy are to be included. 0 means all. Default 0.
    37  *     @type object             $walker          Instance of a custom walker class. Default empty.
    38  *     @type string             $theme_location  Theme location to be used. Must be registered with register_nav_menu()
    39  *                                               in order to be selectable by the user.
    40  *     @type string             $items_wrap      How the list items should be wrapped. Default is a ul with an id and class.
    41  *                                               Uses printf() format with numbered placeholders.
    42  *     @type string             $item_spacing    Whether to preserve whitespace within the menu's HTML. Accepts 'preserve' or 'discard'. Default 'preserve'.
     23 *     @type int|string|WP_Term $menu                 Desired menu. Accepts a menu ID, slug, name, or object. Default empty.
     24 *     @type string             $menu_class           CSS class to use for the ul element which forms the menu. Default 'menu'.
     25 *     @type string             $menu_id              The ID that is applied to the ul element which forms the menu.
     26 *                                                    Default is the menu slug, incremented.
     27 *     @type string             $container            Whether to wrap the ul, and what to wrap it with. Default 'div'.
     28 *     @type string             $container_class      Class that is applied to the container. Default 'menu-{menu slug}-container'.
     29 *     @type string             $container_id         The ID that is applied to the container. Default empty.
     30 *     @type string             $container_aria_label The aria-label attribute that is applied to the container when it's a nav element. Default empty.
     31 *     @type callable|bool      $fallback_cb          If the menu doesn't exist, a callback function will fire.
     32 *                                                    Default is 'wp_page_menu'. Set to false for no fallback.
     33 *     @type string             $before               Text before the link markup. Default empty.
     34 *     @type string             $after                Text after the link markup. Default empty.
     35 *     @type string             $link_before          Text before the link text. Default empty.
     36 *     @type string             $link_after           Text after the link text. Default empty.
     37 *     @type bool               $echo                 Whether to echo the menu or return it. Default true.
     38 *     @type int                $depth                How many levels of the hierarchy are to be included. 0 means all. Default 0.
     39 *     @type object             $walker               Instance of a custom walker class. Default empty.
     40 *     @type string             $theme_location       Theme location to be used. Must be registered with register_nav_menu()
     41 *                                                    in order to be selectable by the user.
     42 *     @type string             $items_wrap           How the list items should be wrapped. Default is a ul with an id and class.
     43 *                                                    Uses printf() format with numbered placeholders.
     44 *     @type string             $item_spacing         Whether to preserve whitespace within the menu's HTML. Accepts 'preserve' or 'discard'. Default 'preserve'.
    4345 * }
    4446 * @return void|string|false Void if 'echo' argument is true, menu output if 'echo' is false.
     
    4951
    5052    $defaults = array(
    51         'menu'            => '',
    52         'container'       => 'div',
    53         'container_class' => '',
    54         'container_id'    => '',
    55         'menu_class'      => 'menu',
    56         'menu_id'         => '',
    57         'echo'            => true,
    58         'fallback_cb'     => 'wp_page_menu',
    59         'before'          => '',
    60         'after'           => '',
    61         'link_before'     => '',
    62         'link_after'      => '',
    63         'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
    64         'item_spacing'    => 'preserve',
    65         'depth'           => 0,
    66         'walker'          => '',
    67         'theme_location'  => '',
     53        'menu'                 => '',
     54        'container'            => 'div',
     55        'container_class'      => '',
     56        'container_id'         => '',
     57        'container_aria_label' => '',
     58        'menu_class'           => 'menu',
     59        'menu_id'              => '',
     60        'echo'                 => true,
     61        'fallback_cb'          => 'wp_page_menu',
     62        'before'               => '',
     63        'after'                => '',
     64        'link_before'          => '',
     65        'link_after'           => '',
     66        'items_wrap'           => '<ul id="%1$s" class="%2$s">%3$s</ul>',
     67        'item_spacing'         => 'preserve',
     68        'depth'                => 0,
     69        'walker'               => '',
     70        'theme_location'       => '',
    6871    );
    6972
     
    177180            $class          = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-' . $menu->slug . '-container"';
    178181            $id             = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : '';
    179             $nav_menu      .= '<' . $args->container . $id . $class . '>';
     182            $aria_label     = ( 'nav' === $args->container && $args->container_aria_label ) ? ' aria-label="' . esc_attr( $args->container_aria_label ) . '"' : '';
     183            $nav_menu      .= '<' . $args->container . $id . $class . $aria_label . '>';
    180184        }
    181185    }
  • trunk/src/wp-includes/widgets/class-wp-nav-menu-widget.php

    r47122 r48349  
    4747        }
    4848
    49         $title = ! empty( $instance['title'] ) ? $instance['title'] : '';
     49        $default_title = __( 'Menu' );
     50        $title         = ! empty( $instance['title'] ) ? $instance['title'] : '';
    5051
    5152        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
     
    5859        }
    5960
    60         $nav_menu_args = array(
    61             'fallback_cb' => '',
    62             'menu'        => $nav_menu,
    63         );
     61        $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     62
     63        /**
     64         * Filters the HTML format of widgets with navigation links.
     65         *
     66         * @since 5.5.0
     67         *
     68         * @param string $format The type of markup to use in widgets with navigation links.
     69         *                       Accepts 'html5', 'xhtml'.
     70         */
     71        $format = apply_filters( 'navigation_widgets_format', $format );
     72
     73        if ( 'html5' === $format ) {
     74            // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     75            $title      = trim( strip_tags( $title ) );
     76            $aria_label = $title ? $title : $default_title;
     77
     78            $nav_menu_args = array(
     79                'fallback_cb'          => '',
     80                'menu'                 => $nav_menu,
     81                'container'            => 'nav',
     82                'container_aria_label' => $aria_label,
     83                'items_wrap'           => '<ul id="%1$s" class="%2$s">%3$s</ul>',
     84            );
     85        } else {
     86            $nav_menu_args = array(
     87                'fallback_cb' => '',
     88                'menu'        => $nav_menu,
     89            );
     90        }
    6491
    6592        /**
  • trunk/src/wp-includes/widgets/class-wp-widget-archives.php

    r47593 r48349  
    4141     */
    4242    public function widget( $args, $instance ) {
    43         $title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Archives' );
     43        $default_title = __( 'Archives' );
     44        $title         = ! empty( $instance['title'] ) ? $instance['title'] : $default_title;
    4445
    4546        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
     
    121122/* ]]> */
    122123</script>
    123 
    124         <?php } else { ?>
    125         <ul>
    126124            <?php
    127             wp_get_archives(
    128                 /**
    129                  * Filters the arguments for the Archives widget.
    130                  *
    131                  * @since 2.8.0
    132                  * @since 4.9.0 Added the `$instance` parameter.
    133                  *
    134                  * @see wp_get_archives()
    135                  *
    136                  * @param array $args     An array of Archives option arguments.
    137                  * @param array $instance Array of settings for the current widget.
    138                  */
    139                 apply_filters(
    140                     'widget_archives_args',
    141                     array(
    142                         'type'            => 'monthly',
    143                         'show_post_count' => $count,
    144                     ),
    145                     $instance
    146                 )
    147             );
     125        } else {
     126            $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     127
     128            /**
     129             * Filters the HTML format of widgets with navigation links.
     130             *
     131             * @since 5.5.0
     132             *
     133             * @param string $format The type of markup to use in widgets with navigation links.
     134             *                       Accepts 'html5', 'xhtml'.
     135             */
     136            $format = apply_filters( 'navigation_widgets_format', $format );
     137
     138            if ( 'html5' === $format ) {
     139                // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     140                $title      = trim( strip_tags( $title ) );
     141                $aria_label = $title ? $title : $default_title;
     142                echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     143            }
    148144            ?>
    149         </ul>
     145
     146            <ul>
     147                <?php
     148                wp_get_archives(
     149                    /**
     150                     * Filters the arguments for the Archives widget.
     151                     *
     152                     * @since 2.8.0
     153                     * @since 4.9.0 Added the `$instance` parameter.
     154                     *
     155                     * @see wp_get_archives()
     156                     *
     157                     * @param array $args     An array of Archives option arguments.
     158                     * @param array $instance Array of settings for the current widget.
     159                     */
     160                    apply_filters(
     161                        'widget_archives_args',
     162                        array(
     163                            'type'            => 'monthly',
     164                            'show_post_count' => $count,
     165                        ),
     166                        $instance
     167                    )
     168                );
     169                ?>
     170            </ul>
     171            <?php if ( 'html5' === $format ) : ?>
     172                </nav>
     173            <?php endif; ?>
     174
    150175            <?php
     176            echo $args['after_widget'];
    151177        }
    152 
    153         echo $args['after_widget'];
    154178    }
    155179
  • trunk/src/wp-includes/widgets/class-wp-widget-categories.php

    r48111 r48349  
    4545        static $first_dropdown = true;
    4646
    47         $title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Categories' );
     47        $default_title = __( 'Categories' );
     48        $title         = ! empty( $instance['title'] ) ? $instance['title'] : $default_title;
    4849
    4950        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
     
    110111            <?php
    111112        } else {
     113            $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     114
     115            /**
     116             * Filters the HTML format of widgets with navigation links.
     117             *
     118             * @since 5.5.0
     119             *
     120             * @param string $format The type of markup to use in widgets with navigation links.
     121             *                       Accepts 'html5', 'xhtml'.
     122             */
     123            $format = apply_filters( 'navigation_widgets_format', $format );
     124
     125            if ( 'html5' === $format ) {
     126                // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     127                $title      = trim( strip_tags( $title ) );
     128                $aria_label = $title ? $title : $default_title;
     129                echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     130            }
    112131            ?>
    113         <ul>
    114             <?php
    115             $cat_args['title_li'] = '';
    116 
    117             /**
    118              * Filters the arguments for the Categories widget.
    119              *
    120              * @since 2.8.0
    121              * @since 4.9.0 Added the `$instance` parameter.
    122              *
    123              * @param array $cat_args An array of Categories widget options.
    124              * @param array $instance Array of settings for the current widget.
    125              */
    126             wp_list_categories( apply_filters( 'widget_categories_args', $cat_args, $instance ) );
    127             ?>
    128         </ul>
     132
     133            <ul>
     134                <?php
     135                $cat_args['title_li'] = '';
     136
     137                /**
     138                 * Filters the arguments for the Categories widget.
     139                 *
     140                 * @since 2.8.0
     141                 * @since 4.9.0 Added the `$instance` parameter.
     142                 *
     143                 * @param array $cat_args An array of Categories widget options.
     144                 * @param array $instance Array of settings for the current widget.
     145                 */
     146                wp_list_categories( apply_filters( 'widget_categories_args', $cat_args, $instance ) );
     147                ?>
     148            </ul>
     149
     150            <?php if ( 'html5' === $format ) : ?>
     151                </nav>
     152            <?php endif; ?>
     153
    129154            <?php
    130155        }
  • trunk/src/wp-includes/widgets/class-wp-widget-meta.php

    r47593 r48349  
    4343     */
    4444    public function widget( $args, $instance ) {
    45         $title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Meta' );
     45        $default_title = __( 'Meta' );
     46        $title         = ! empty( $instance['title'] ) ? $instance['title'] : $default_title;
    4647
    4748        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
     
    5354            echo $args['before_title'] . $title . $args['after_title'];
    5455        }
     56
     57        $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     58
     59        /**
     60         * Filters the HTML format of widgets with navigation links.
     61         *
     62         * @since 5.5.0
     63         *
     64         * @param string $format The type of markup to use in widgets with navigation links.
     65         *                       Accepts 'html5', 'xhtml'.
     66         */
     67        $format = apply_filters( 'navigation_widgets_format', $format );
     68
     69        if ( 'html5' === $format ) {
     70            // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     71            $title      = trim( strip_tags( $title ) );
     72            $aria_label = $title ? $title : $default_title;
     73            echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     74        }
    5575        ?>
    56             <ul>
     76
     77
     78        <ul>
    5779            <?php wp_register(); ?>
    5880            <li><?php wp_loginout(); ?></li>
    5981            <li><a href="<?php echo esc_url( get_bloginfo( 'rss2_url' ) ); ?>"><?php _e( 'Entries feed' ); ?></a></li>
    6082            <li><a href="<?php echo esc_url( get_bloginfo( 'comments_rss2_url' ) ); ?>"><?php _e( 'Comments feed' ); ?></a></li>
     83
    6184            <?php
    6285            /**
     
    81104            wp_meta();
    82105            ?>
    83             </ul>
    84             <?php
    85106
    86             echo $args['after_widget'];
     107        </ul>
     108
     109        <?php if ( 'html5' === $format ) : ?>
     110            </nav>
     111        <?php endif; ?>
     112
     113        <?php
     114        echo $args['after_widget'];
    87115    }
    88116
  • trunk/src/wp-includes/widgets/class-wp-widget-pages.php

    r47593 r48349  
    4141     */
    4242    public function widget( $args, $instance ) {
    43         $title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Pages' );
     43        $default_title = __( 'Pages' );
     44        $title         = ! empty( $instance['title'] ) ? $instance['title'] : $default_title;
    4445
    4546        /**
     
    9091                echo $args['before_title'] . $title . $args['after_title'];
    9192            }
     93
     94            $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     95
     96            /**
     97             * Filters the HTML format of widgets with navigation links.
     98             *
     99             * @since 5.5.0
     100             *
     101             * @param string $format The type of markup to use in widgets with navigation links.
     102             *                       Accepts 'html5', 'xhtml'.
     103             */
     104            $format = apply_filters( 'navigation_widgets_format', $format );
     105
     106            if ( 'html5' === $format ) {
     107                // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     108                $title      = trim( strip_tags( $title ) );
     109                $aria_label = $title ? $title : $default_title;
     110                echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     111            }
    92112            ?>
    93         <ul>
    94             <?php echo $out; ?>
    95         </ul>
     113
     114            <ul>
     115                <?php echo $out; ?>
     116            </ul>
     117
     118            <?php if ( 'html5' === $format ) : ?>
     119                </nav>
     120            <?php endif; ?>
     121
    96122            <?php
    97123            echo $args['after_widget'];
  • trunk/src/wp-includes/widgets/class-wp-widget-recent-comments.php

    r48109 r48349  
    8383        $output = '';
    8484
    85         $title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Comments' );
     85        $default_title = __( 'Recent Comments' );
     86        $title         = ( ! empty( $instance['title'] ) ) ? $instance['title'] : $default_title;
    8687
    8788        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
     
    124125        $first_instance     = false;
    125126
     127        $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     128
     129        /**
     130         * Filters the HTML format of widgets with navigation links.
     131         *
     132         * @since 5.5.0
     133         *
     134         * @param string $format The type of markup to use in widgets with navigation links.
     135         *                       Accepts 'html5', 'xhtml'.
     136         */
     137        $format = apply_filters( 'navigation_widgets_format', $format );
     138
     139        if ( 'html5' === $format ) {
     140            // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     141            $title      = trim( strip_tags( $title ) );
     142            $aria_label = $title ? $title : $default_title;
     143            $output    .= '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     144        }
     145
    126146        $output .= '<ul id="' . esc_attr( $recent_comments_id ) . '">';
    127147        if ( is_array( $comments ) && $comments ) {
     
    142162        }
    143163        $output .= '</ul>';
     164
     165        if ( 'html5' === $format ) {
     166            $output .= '</nav>';
     167        }
     168
    144169        $output .= $args['after_widget'];
    145170
  • trunk/src/wp-includes/widgets/class-wp-widget-recent-posts.php

    r47593 r48349  
    4646        }
    4747
    48         $title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Posts' );
     48        $default_title = __( 'Recent Posts' );
     49        $title         = ( ! empty( $instance['title'] ) ) ? $instance['title'] : $default_title;
    4950
    5051        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
     
    8586        }
    8687        ?>
     88
    8789        <?php echo $args['before_widget']; ?>
     90
    8891        <?php
    8992        if ( $title ) {
    9093            echo $args['before_title'] . $title . $args['after_title'];
    9194        }
     95
     96        $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     97
     98        /**
     99         * Filters the HTML format of widgets with navigation links.
     100         *
     101         * @since 5.5.0
     102         *
     103         * @param string $format The type of markup to use in widgets with navigation links.
     104         *                       Accepts 'html5', 'xhtml'.
     105         */
     106        $format = apply_filters( 'navigation_widgets_format', $format );
     107
     108        if ( 'html5' === $format ) {
     109            // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     110            $title      = trim( strip_tags( $title ) );
     111            $aria_label = $title ? $title : $default_title;
     112            echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     113        }
    92114        ?>
     115
    93116        <ul>
    94117            <?php foreach ( $r->posts as $recent_post ) : ?>
     
    110133            <?php endforeach; ?>
    111134        </ul>
     135        <?php if ( 'html5' === $format ) : ?>
     136            </nav>
     137        <?php endif; ?>
     138
    112139        <?php
    113140        echo $args['after_widget'];
  • trunk/src/wp-includes/widgets/class-wp-widget-rss.php

    r47774 r48349  
    9595            echo $args['before_title'] . $title . $args['after_title'];
    9696        }
     97
     98        $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
     99
     100        /**
     101         * Filters the HTML format of widgets with navigation links.
     102         *
     103         * @since 5.5.0
     104         *
     105         * @param string $format The type of markup to use in widgets with navigation links.
     106         *                       Accepts 'html5', 'xhtml'.
     107         */
     108        $format = apply_filters( 'navigation_widgets_format', $format );
     109
     110        if ( 'html5' === $format ) {
     111            // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
     112            $title      = trim( strip_tags( $title ) );
     113            $aria_label = $title ? $title : __( 'RSS Feed' );
     114            echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
     115        }
     116
    97117        wp_widget_rss_output( $rss, $instance );
     118
     119        if ( 'html5' === $format ) {
     120            echo '</nav>';
     121        }
     122
    98123        echo $args['after_widget'];
    99124
Note: See TracChangeset for help on using the changeset viewer.