Make WordPress Core

Opened 10 months ago

Closed 7 months ago

#63581 closed defect (bug) (maybelater)

paginate_links: The current page should be an a element, not a span element

Reported by: wildworks's profile wildworks Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: General Keywords: 2nd-opinion has-patch
Focuses: accessibility Cc:

Description

Originally reported at GB 70439

The paginate_links function outputs the current page as a span element. Example:

echo ( array( 'current' => 1, 'total' => 10 ) );

<span aria-current="page" class="page-numbers current">1</span>
<a class="page-numbers" href="">2</a>
<a class="page-numbers" href="">3</a>
<span class="page-numbers dots">&hellip;</span>
<a class="page-numbers" href="">10</a>
<a class="next page-numbers" href="">Next &raquo;</a>

However, this may cause an accessibility problem for screen reader users, as the current page is not focusable.

Ideally, the HTML markup should be:

<a aria-current="page" class="page-numbers current" href="">1</a>
<a class="page-numbers" href="">2</a>
<a class="page-numbers" href="">3</a>
<span class="page-numbers dots">&hellip;</span>
<a class="page-numbers" href="">10</a>
<a class="next page-numbers" href="">Next &raquo;</a>

However, this change will impact many themes that include a span element as part of their CSS selectors.

I would like to clarify whether the current HTML really has problems from the viewpoint of accessibility in the first place. I look forward to feedback from people who are knowledgeable about accessibility.

Change History (13)

#1 @sabernhardt
10 months ago

  • Keywords 2nd-opinion added

The W3C Pagination and Breadcrumbs pages follow one of Scott O'Hara's breadcrumb pattern examples. Scott's third example assigns aria-current to the li element, without a link, and the usage notes present arguments both for and against linking.

Changing the function's default behavior now would require a clear and significant improvement. I do not think the linked pattern is much better, but others might disagree.

However, choosing to link the current page probably could be easier. I only know one way to alter the markup: using preg_replace() within the paginate_links_output filter.

#2 @yashjawale
10 months ago

In my opinion, the linked approach seems to be a better approach as the same is used in the recommended markup structure at https://design-system.w3.org/components/pagination.html

The original issue description mentions the same

However, changing the current markup would also cause issues with existing themes that expect current page to use the <span> tag...

#3 @iamadisingh
10 months ago

I've analyzed the paginate_links() function in https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/general-template.php#L4627-4807 and would like to propose a backwards-compatible solution.

Current Issue
The current page is output as a <span> element:

<?php
$page_links[] = sprintf(
    '<span aria-current="%s" class="page-numbers current">%s</span>',
    esc_attr( $args['aria_current'] ),
    $args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
);

This creates accessibility issues as screen reader users cannot focus on the current page indicator.

Proposed Solution
Add a new link_current parameter to the $defaults array with backwards compatibility:

<?php
$defaults = array(
    // ...existing parameters...
    'link_current' => false, // NEW: Whether to link the current page for better accessibility
);

Then update the current page logic to conditionally create a link:

<?php
if ( $n === $current ) :
    if ( $args['link_current'] ) {
        // Generate current page URL (same as current page)
        $current_link = str_replace( '%_%', 1 === $n ? '' : $args['format'], $args['base'] );
        $current_link = str_replace( '%#%', $n, $current_link );
        if ( $add_args ) {
            $current_link = add_query_arg( $add_args, $current_link );
        }
        $current_link .= $args['add_fragment'];
        
        $page_links[] = sprintf(
            '<a aria-current="%s" class="page-numbers current" href="%s">%s</a>',
            esc_attr( $args['aria_current'] ),
            esc_url( apply_filters( 'paginate_links', $current_link ) ),
            $args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
        );
    } else {
        // Keep existing span behavior as default (backwards compatibility)
        $page_links[] = sprintf(
            '<span aria-current="%s" class="page-numbers current">%s</span>',
            esc_attr( $args['aria_current'] ),
            $args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
        );
    }
    $dots = true;

This ticket was mentioned in PR #9088 on WordPress/wordpress-develop by @iamadisingh.


10 months ago
#4

  • Keywords has-patch added

Trac ticket: https://core.trac.wordpress.org/ticket/63581

Summary

Adds a new link_current parameter to the paginate_links() function to improve accessibility by allowing the current page to be output as a focusable link element instead of a span.

Problem

The current implementation of paginate_links() outputs the current page as a <span> element, which creates accessibility issues for screen reader users as the current page indicator is not focusable. This makes it difficult for users navigating with keyboards or assistive technologies to interact with the current page element.

Current behavior:

<span aria-current="page" class="page-numbers current">2</span>

Desired behavior:

<a aria-current="page" class="page-numbers current" href="...">2</a>

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


9 months ago

#6 @joedolson
9 months ago

Screen readers will announce aria-current on a list item, it's just that you have to be reading the text, rather than navigating by tab. I question whether there's any benefit to a user to add the current page into the tab list.

From a screen reader user standpoint, you navigate in reading mode if you want to discover information; and you're more likely to navigate by tab if you're specifically looking for an action to take. The current page is information, but it's very unlikely to be an action the user wants to take.

If the pagination output was always consistent, containing the same links in all cases, then I'd think that having this present would be more important; but the pagination links change in every view, so they are never totally consistent.

I think that adding a parameter that would support having this linked is fine; but I'm not convinced this is a problem that needs to be solved. Increasing the complexity by making this an additional option seems to have a pretty marginal benefit.

#7 @wildworks
9 months ago

Thank you for your feedback. I realized that the current implementation is not necessarily a problem.

I propose to close this issue as maybelater. Developers who really want to change the HTML output can do so using paginate_links_output filter and preg_replace() function.

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


9 months ago

#9 @joedolson
9 months ago

I will say that I'd find this more valuable if we could comfortably change the defaults, but I know that changing the element on the current link would break layouts on a lot of themes.

@wildworks Is it feasible to add this and change the default for block themes only? That might make it more valuable, overall. As is, I think that because it would require theme developers to choose the option, the scope of impact is very low. And, as you say, it can already be done.

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


8 months ago

#11 @wildworks
8 months ago

@wildworks Is it feasible to add this and change the default for block themes only?

Does this mean that the output of the paginate_links() function itself will automatically change when a block theme is enabled?

Personally, I find it a bit confusing that the HTML output changes depending on the theme, even though the same function is used.

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


7 months ago

#13 @joedolson
7 months ago

  • Milestone Awaiting Review deleted
  • Resolution set to maybelater
  • Status changed from new to closed

Following discussion in the accessibility bug scrub, we agreed to close this as a maybelater. While right now there is no clear value to the change, best practices in accessibility are always a moving target, and that may not still be true in the future.

Note: See TracTickets for help on using tickets.