Make WordPress Core

Opened 4 weeks ago

Last modified 21 hours ago

#63581 new defect (bug)

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

Reported by: wildworks's profile wildworks Owned by:
Milestone: Awaiting Review 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 (7)

#1 @sabernhardt
4 weeks 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
3 weeks 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
3 weeks 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.


3 weeks 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.


31 hours ago

#6 @joedolson
31 hours 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
21 hours 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.

Note: See TracTickets for help on using tickets.