Skip to content
This repository has been archived by the owner on Oct 1, 2024. It is now read-only.

Links should either be labeled or removed from tabbing #49

Closed
chaance opened this issue Nov 13, 2019 · 7 comments
Closed

Links should either be labeled or removed from tabbing #49

chaance opened this issue Nov 13, 2019 · 7 comments
Labels
🗄 area/interface This affects the public interface 💪 phase/solved Post is done 🧒 semver/minor This is backwards-compatible change 🐛 type/bug This is a problem 🦋 type/enhancement This is great to have

Comments

@chaance
Copy link

chaance commented Nov 13, 2019

Subject of the issue

Generated header links are given an aria-hidden attribute, which removes them from the accessibility tree. If that is the goal, then they should also have a tabindex of -1. Currently, when a user tabs into a header link, VoiceOver reads back the name of the document rather than the heading text.

Alternatively (and, I believe, a better experience), you could consider either:

  • Wrapping the link around the heading's text rather than having it adjacent to the text (and unhide it)
<h2 id="what-exactly-is-a-portal">
  <a href="#what-exactly-is-a-portal">
    <span class="icon icon-link"></span>What exactly is a portal?
  </a>
</h2>
  • Adding an aria-labelledby attribute that points back to the generated ID of the heading.
<h2 id="what-exactly-is-a-portal">
  <a href="#what-exactly-is-a-portal" aria-labelledby="what-exactly-is-a-portal">
    <span class="icon icon-link"></span>
  </a>What exactly is a portal?
</h2>

The latter won't be as widely supported, but the former will probably require breaking a lot of existing projects and a new approach for handling styles. I'd probably suggest #2 until introducing a new major version.

I'm happy to submit a PR if someone wants to weigh in on the preferred approach here!

Your environment

  • MacOS Catalina w/ VoiceOver
  • N/A
  • N/A

Steps to reproduce

Enable VoiceOver under Accessibility settings in System Preferences and tab through a site with generated headers.

Expected behaviour

Screen readers should read back what's in the heading when it receives focus, or they should be removed from focus altogether

Actual behaviour

Screen readers announce the title of the page or active landmark if one exists, creating a confusing experience.

@chaance chaance added 🐛 type/bug This is a problem 🙉 open/needs-info This needs some more info labels Nov 13, 2019
@wooorm
Copy link
Member

wooorm commented Nov 13, 2019

Heya! I remember a while back we talked about accessibility and weren't totally sure what the preferred method would be, I couldn't find any articles on this then, do you know of any?

Regarding your first solution, that is already an option with the 'wrap' behavior, right?

I'm up for changing the default to wrap, but would still like to make append and prepend accessible as well, any suggestions for those?

@chaance
Copy link
Author

chaance commented Nov 13, 2019

I'll look around – I'm not 100% sure that this is covered in spec, but I do know that there is an axe testing rule that catches things like this for the reasons I specified: https://dequeuniversity.com/rules/axe/3.3/aria-hidden-focus

A related SO exchange: https://stackoverflow.com/a/52208194

Basically, if you want to hide the links, it's best to remove their tab order. That underlying decision though may be subjective, and so I'd argue that folks using SRs and keyboards to navigate should be able to access the links for the same reasons anyone else might need them. Didn't know about the wrap option, which is great! In that case I'd advocate for using aria-labelledby as the default.

Others may disagree. I forgot that, just reading through the content, a header will be read twice if you remove aria-hidden, but I'm unsure how either NVDA or JAWS might deal with having both a label and a hidden attribute. But at least with VoiceOver, this works great:

<h2 id="what-exactly-is-a-portal">
  <a href="#what-exactly-is-a-portal" aria-labelledby="what-exactly-is-a-portal" aria-hidden="true">
    <span class="icon icon-link"></span>
  </a>What exactly is a portal?
</h2>

I don't know of any articles unfortunately. Perhaps I should write one! Here is a relevant bit of the spec on how this affects SR users. https://www.w3.org/TR/WCAG20-TECHS/ARIA7.html

@chaance
Copy link
Author

chaance commented Nov 13, 2019

An even better solution suggested by @smhigley would be to pull the link out of the heading tag altogether. If you just need the heading for positioning purposes, this should work better than trying to hide the link!

<div> <!-- outer div for positioning the link -->
  <a href="#what-exactly-is-a-portal" aria-labelledby="what-exactly-is-a-portal">
    <span class="icon icon-link" aria-hidden="true"></span>
  </a>
  <h2 id="what-exactly-is-a-portal">What exactly is a portal?</h2>
</div>

@wooorm
Copy link
Member

wooorm commented Nov 14, 2019

@chaance
Copy link
Author

chaance commented Nov 14, 2019

Thanks for the reference @wooorm, I was struggling to find good examples in the wild. His approach is also good, as the link and the heading will both be announced.

<div>
  <h2 id="what-exactly-is-a-portal" tabindex="-1">What exactly is a portal?</h2>
  <a href="#what-exactly-is-a-portal">
    <span class="visually-hidden">Read the "What exactly is a portal?" section</span>
    <span class="icon icon-link" aria-hidden="true"></span>
  </a>
</div>

@wooorm
Copy link
Member

wooorm commented Nov 14, 2019

I love this, but the downside is that it wouldnt be a good option for a default, because of the English prefix and suffix 🤔

wooorm added a commit that referenced this issue Mar 23, 2020
wooorm added a commit that referenced this issue Mar 23, 2020
wooorm added a commit that referenced this issue Mar 23, 2020
wooorm added a commit that referenced this issue Mar 23, 2020
@wooorm
Copy link
Member

wooorm commented Mar 23, 2020

Alright, pushed a couple of things so all of the above ideas are now possible!

For your last example, these options (pseudocode) should do the trick:

var toString = require('mdast-util-to-string')
var h = require('hastscript')

var options = {
  behavior: 'after',
  group: h('div'),
  content: function (node) {
    return [
      h('span.visually-hidden', 'Read the "', toString(node), '" section'),
      h('span.icon.icon-link', {ariaHidden: true})
    ]
  }
}

@wooorm wooorm closed this as completed Mar 23, 2020
@wooorm wooorm added ⛵️ status/released 🐛 type/bug This is a problem 🗄 area/interface This affects the public interface 🦋 type/enhancement This is great to have 🧒 semver/minor This is backwards-compatible change and removed 🐛 type/bug This is a problem 🙉 open/needs-info This needs some more info labels Mar 23, 2020
wooorm added a commit to rehypejs/rehype-autolink-headings that referenced this issue Jun 21, 2020
@wooorm wooorm added the 💪 phase/solved Post is done label Aug 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🗄 area/interface This affects the public interface 💪 phase/solved Post is done 🧒 semver/minor This is backwards-compatible change 🐛 type/bug This is a problem 🦋 type/enhancement This is great to have
Development

No branches or pull requests

2 participants