Skip to content

HTML Anchors - The Hidden Accessibility Bug #70085

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
3 of 6 tasks
anevins12 opened this issue May 8, 2025 · 11 comments
Open
3 of 6 tasks

HTML Anchors - The Hidden Accessibility Bug #70085

anevins12 opened this issue May 8, 2025 · 11 comments
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Needs Accessibility Feedback Need input from accessibility Needs Decision Needs a decision to be actionable or relevant [Type] Bug An existing feature does not function as intended

Comments

@anevins12
Copy link
Contributor

anevins12 commented May 8, 2025

Description

The HTML Anchor feature inside "Advanced" block settings helps people set links to point to those anchors.

For example, adding a HTML Anchor of "example" on a heading and then adding a separate link that points to "#example" will link the two. Activating the link will cause the browser to scroll to that content and keyboard users can now navigate from that content.

However, some other users relying on assistive technologies won't be moved to that content. That's because DOM Anchors, or fragment navigation, alone isn't enough to move focus to non-interactive content.

An example problem:

<a href="#example"> Find our example </a>
...
<h2 id="example"> Our example </h2>

Assistive technologies need not only to have the browser scroll to content, but they must "reach" it. If they don't, then some users will either face a broken link or an annoyingly difficult one.

Examples of inaccessible experiences include:

  • iOS VoiceOver users not being able to traverse to those links in linear reading order (via SWIPE RIGHT).
  • Same as above for iOS Bluetooth Keyboard (and full keyboard access turned on).
  • Windows Screen Magnifier users being reset to the top-left of their viewport with Keyboard Focus turned on.

Non-interactive content, or static content, such as headings and paragraphs, are not focusable by default. There are few criteria in the HTML spec that makes these focusable, but in most cases it only involves adding the tabindex attribute.

The tabindex attribute has to have a relevant value, which in this case is -1. This is because 0 and any positive number convolutes the sequential focus navigation order (the way some folks traverse the page such as via the TAB key.)

An example solution:

<a href="#example"> Find our example </a>
...
<h2 id="example" tabindex="-1"> Our example </h2>

Ideally, JavaScript would add tabindex="-1" to the DOM Anchor when users activate the link. I have developed an NPM package for this if you wish to use it. However, I expect that this is not possible as I understand WP does not add JS to the front-end of people's sites.
Here's the package just in case it's useful: https://www.npmjs.com/package/@tabable/accessible-links

That doesn't mean that fixing this is not possible. We may just need to add tabindex="-1" to the HTML when DOM Anchors are used on non-interactive elements (and where tabindex="-1" isn't already present.)

If you wish to read further into this topic see this codepen: https://codepen.io/Tab-able/full/dPPPONR

Step-by-step reproduction instructions

  1. Edit a page.
  2. Edit an existing block.
  3. Go to the "Block" tab panel settings.
  4. Activate the "Advanced" disclosure.
  5. Add a word to the "HTML Anchor" field.
  6. Add a link elsewhere on the page.
  7. Set the link URL to match the HTML Anchor value (prefixed with a #).
  8. View the page.
  9. Activate the link you added.

Screenshots, screen recording, code snippet

Image

If you don't have the assistive technologies to test this on, you can simply open the console and watch the active element (document.activeElement).

When the activeElement returns body or document after the link has been activated, it means that focus is being "lost" and assistive technologies tracking focus will also become disoriented.

The expected behavior is that activeElement returns the element that has the DOM/HTML anchor.

Environment info

WordPress 6.8.1:

  • Tracking document.activeElement on Chrome latest
  • Windows Screen Magnifier with Keyboard Focus tracking turned on: Viewport resets to the top-left corner.
  • JAWS 2023 & Chrome 124: The page structure is reannounced starting with the page title.
  • iOS 18.3.2 VoiceOver & Safari: Focus is reset to the top of the page.
  • iOS 17.4 VoiceOver & Safari: Focus is reset to the top of the page.
  • iOS 17.4 Full Keyboard Access turned on, external keyboard & Safari: Focus is reset to the top of the page.
  • Windows 11 Narrator on Edge 136: Focus is reset to prior landmark when browser heuristics are disrupted, such as when adding custom stylesheets.

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure
@anevins12 anevins12 added the [Type] Bug An existing feature does not function as intended label May 8, 2025
@Mamaduka Mamaduka added [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Needs Testing Needs further testing to be confirmed. labels May 8, 2025
@t-hamano
Copy link
Contributor

t-hamano commented May 9, 2025

Thanks for the report. I was able to replicate the issue with the following environment:

  • OS; Windows 11
  • Screenreader: NVDA 2024.4.2.3753
  • Browser: Chrome 135.0.7049.116, Firefox 138.0.1 (64-bit)

@t-hamano t-hamano removed the Needs Testing Needs further testing to be confirmed. label May 9, 2025
@Infinite-Null
Copy link
Contributor

Infinite-Null commented May 10, 2025

Hi @t-hamano and @anevins12,

We could enhance the addSaveProps function, which is responsible for adding id to elements in packages/block-editor/src/hooks/anchor.js, to also add tabIndex="-1" to elements after line 126:

extraProps.id = attributes.anchor === '' ? null : attributes.anchor;

extraProps.tabIndex = -1;

This will add tabIndex as -1.

Image

Image

Image

I'm happy to implement this change in a PR if this approach seems appropriate.

@github-actions github-actions bot added the [Status] In Progress Tracking issues with work in progress label May 12, 2025
@t-hamano
Copy link
Contributor

We could enhance the addSaveProps function, which is responsible for adding id to elements in packages/block-editor/src/hooks/anchor.js, to also add tabIndex="-1" to elements after line 126:

I'm not sure if this approach is ideal.

  • I'm not sure if it's acceptable from an a11y perspective to make non-interactive elements focusable by default.
  • The element with the anchor may not be a block.

I think we first need to clarify whether this issue is really a blocker for WordPress to [comply with WCAG 2.2 AA](I think we first need to clarify whether this issue is really a blocker for WordPress to comply with WCAG 2.2 AA, and whether it is a problem that should be solved by WordPress itself. If it is really the case, I think this issue should be fixed as a standard in core.

By the way, there is front-end JS code in core to add skip links to block themes:

https://github.com/t-hamano/wordpress-develop/blob/a04a13a1d0039b6054a4276def28eedecc9cb9ad/src/wp-includes/theme-templates.php#L109

I think this is an example of front-end accessibility improvements being implemented as default in core.

@t-hamano t-hamano added Needs Decision Needs a decision to be actionable or relevant Needs Accessibility Feedback Need input from accessibility labels May 13, 2025
@anevins12
Copy link
Contributor Author

A quick note: This is not a WCAG failure.

@Infinite-Null
Copy link
Contributor

Sure @t-hamano, I will close my PR, and I will wait for a consensus

@t-hamano t-hamano removed the [Status] In Progress Tracking issues with work in progress label May 13, 2025
@anevins12
Copy link
Contributor Author

@t-hamano Forgive me for not being familiar with the process here, are you asking me for answers or are you expecting a discussion to come out of the Wordpress accessibility slack channel?

@t-hamano
Copy link
Contributor

@aNevin This issue already has the "Needs Accessibility Feedback" label, but let's ping some accessibility experts and get their opinions.

cc @joedolson @alexstine @afercia

@alexstine
Copy link
Contributor

Anchor links should actually work by default so this feels like a browser bug to me. The only condition where this wouldn't work is if we're overriding the default with JavaScript and using something like the scrollTo type libraries. You should not need a tab index of -1 here but I also don't see it as something we shouldn't add if this is indeed a bug. I'll wait for input from Joe, I think he was the closest to the skip link focus fix. IOS over the years has never worked properly so honestly, if it is only one screen reader or browser, do not think anyone should go out of the way to fix it or we'll end up with our role="document" issue, thanks to myself for that one.

Thanks.

@anevins12
Copy link
Contributor Author

anevins12 commented May 15, 2025

Hey @alexstine , thanks for chiming in and giving some great insights.

Is it a browser or AT bug?

This isn't a bug with an assistive technology or browser. We may be seeing different levels of affected technologies, but they share the same underlying behavior. That behavior is related to how "focus" is being managed (or not managed), and browsers are correctly following the HTML specification.

Let me explain.

It's true that anchor links should work by default. In fact, they do work. People click on them and the browser scrolls to that section. Additionally, keyboard users having activated the link can traverse from that destination with the TAB key.

I get it, it's easy to dismiss this because surely we can't have been wrong all of this time? But many assistive tech behave strangely and we should have caught onto this by now.

Have you never thought what's going on when you activate an anchor and JAWS for example unexpectedly announces page <title> and resets the virtual cursor?

These are symptoms of something going wrong.

The issue isn't technically a problem with the link, but it's when we're leading people to non-interactive content.

Try it out in any browser of your choice by watching the activeElement property on the global document object and then click on an anchor.

If you see that the activeElement returns body or document that means that the browser is not moving "focus" to that element.

When we're looking at the behavior of assistive technologies, we're only looking at the symptom of the underlying focus problem. It's true that some assistive technologies are more significantly affected, such as iOS VoiceOver or iOS Bluetooth Keyboard users. That doesn't mean the assistive technologies are the problem.

I would ask that folks do pay attention to the technologies I've tested with, while also bearing in mind that the technologies are not exhaustive. The technologies I provided were those I had around at the time and did a good job at demonstrating examples of people with disabilities affected.

Let's go further and really get to the bottom of this idea of it being a browser or assistive technology bug.

Let's turn to the HTML specification.

The documentation around the interaction of anchors and what happens to assistive tech is somewhat in a grey area. This because it's not a requirement of the a element per se. It's something to do with an interaction model. We'll look directly at the User Interaction Data model.

This model primarily describes how keyboard is managed by the browser and that part is not so relevant to this discussion. However, we are interested in "system focus" and how that relates to assistive technologies (that track "focus").

Looking at the table that identifies what is considered a "focusable area", we can make out that there matching normative requirements:

In our context of making a non-interactive element focusable, we only need to concern ourself with the first requirement regarding the use of the tabindex attribute. The rest come by default with non-interactive elements.

We can use evidence of the behavior of the activeElement property and the HTML specification to rule out this being a browser bug.

Note: The HTML specification has not changed on what is considered focusable elements.

Does tabindex="-1" make elements focusable by default?

That's a great point to bring up @t-hamano and I can understand the concern. Making a wrong move here and we'll have made the experience of keyboard and assistive technology users worse.

There's 3 values we should always have in mind with the tabindex attribute:

  • -1 makes an element programmatically focusable, meaning that focus could be moved to it. But it doesn't make that content focusable by default.
  • 0 makes an element programmatically focusable and makes it focusable by default. It's useful for when you're unable to use a real interactive element, for example <div role="button" tabindex="0"> A button </div>. This is not usually recommended for non-interactive elements.
  • 1 or any positive value performs the same as 0, but additionally disrupts the natural flow of interactive content (or the "sequential focus navigation order"). For instance, tabindex="1" could mean that keyboard users reach this control first thing on the page, when in fact it is meant to be lower down in the navigation order.

To resolve this accessibility barrier, we're only suggesting to use the tabindex="-1" value when a HTML Anchor is added onto a non-interactive element. This is the least disruptive and most appropriate value because it does not make something focusable "by default."

The element with the anchor may not be a block.
That's another great point. A fantastic solution would be for us to tackle the issue in one area and have it reverberate to all areas of concern.

While we can agree with wanting to invest the smallest effort for the widest result, we still need to break this multifaceted problem into smaller tasks.

That's why this ticket was created for the HTML Anchor setting that is directly part of a block. The setting doesn't appear in other contexts.

Is it a blocker for WCAG 2.2 AA
It depends what technologies you test against. Typically auditors test the A11y API from a desktop browser, then a desktop screen reader, and finally a mobile screen reader on a browser. This approach wouldn't fail WCAG.

However, if you included an iOS Bluetooth keyboard user into the audit then this would fail SC 2.1.1 Keyboard under Conformance Requirement 4 (5.2.4) "Only Accessibility-Supported Ways of Using Technologies".

This is because the link, while technically clickable, is not "operable" to those users. They can't get to the content via the links; the links do not work.

I purposely did not start this topic on conformance and instead tried to keep focus on real people with disabilities. Some of these people are:

  • People with blindness that rely on a screen reader to dictate information and structure on a page, and more importantly to help navigate to content.
  • People with low vision that rely on a screen magnifier to read information and navigate the page. Those turning on focus tracking expect their viewport to move to the destination of the link.
  • People with limited mobility that prefer to navigate and interact with an iOS device via a bluetooth keyboard.

I suspect that other assistive technologies are similarly affected, such as ZoomText for people with low vision or learning disabilities. If you have any other assistive tech that tracks focus please try this issue out.

Does WordPress only tackle accessibility issues when WCAG conformance is at play?

There is front-end JS code in core to add skip links to block themes
Noted. Skip links have the same kind of problem I'm discussing here, but I will address that in core themes separately.

Should we go out of our way to fix it and risk what happened with role="document"?
I don't have knowledge on what happened with the role="document" resolution, but it sounds like we're trying to avoid making a similar mistake.

@alexstine Would you be able to go into more detail as to what happened, and how we can avoid that in future?

@alexstine
Copy link
Contributor

@anevins12 I am a blind screen reader user and I use assistive technology in my day to day work. When I tested your exact steps, it seems that this works fine in Firefox with NVDA. I attached an HTML anchor to a paragraph block and then added a link. When I viewed the post on the front-end and selected the link, my screen reader started reading the paragraph text. This is why I'm thinking this bug may be more specific to assistive technology/browser combinations because I cannot reproduce this bug. Furthermore, I commented on Voiceover because Voiceover on IOS and Mac has always been the Internet Explorer of screen readers. I mean, it has a lot of the time never followed the ARIA specification like other screen readers and has often times required some very hacky code to force it to operate the same way as Windows-based screen readers.

As far as the role="document" stuff, at one time, JAWS could not switch to forms mode in a situation where you had contentEditable="true" and role="group". I changed this role to role="document" to fix the bug but did not understand what kind of bugs would rain down on me later. I regret this choice to this day but it has never been reverted due to the danger of further unforeseen consequences.

I've been a long time contributor to WordPress, since 2016 right after losing my vision, but stepped back last year due to some of the very vocal accessibility hate throughout the project. I feel like things are slowly starting to get better but in large, not involved these days. Most of the time, I'll still respond to at mentions/pings by others. :)

Hope this helps. Thanks.

@anevins12
Copy link
Contributor Author

anevins12 commented May 20, 2025

Updated environment info to include additional problematic AT:

Windows 11 Narrator on Edge 136: Focus is reset to prior landmark.

This issue seems to arise when the browser’s heuristics are disrupted, such as when people with cognitive disabilities apply custom stylesheets.

The stylesheets, theme, and content will likely differ depending on the WordPress website. For instance, when I added img { max-width: 50% !important} while also linearising the page, it was enough to throw off Edge.

It seems we agree that browsers should not introduce their own heuristics that deviate from established standards. They are intervening because there is a gap between our implementation and what users need.

Would it be unreasonable to expect WordPress developers to take some responsibility and follow interaction models outlined in the HTML specification?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Needs Accessibility Feedback Need input from accessibility Needs Decision Needs a decision to be actionable or relevant [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants