6

I have a simple text that gets updated on an action and I want that to be announced by the screen reader. But I don't want that text to be visible on the web page. I tried display: none and visibility: hidden, but seems like they are not accessible by the screen reader softwares. I found a way to make this work - that is by absolute positioning the element all the way with negative 999999 value which will make it off screen and hidden from the webpage. I am not really a fan of this solution. Is there a more elegant way to achieve this?

<span class="aria-invisible" aria-live="polite">5 selections have been made.</span>

.aria-invisible {
   display: none; //either of these two attributes
   visibility: hidden;
}
Donut
  • 185
  • 1
  • 9

4 Answers4

10

A better solution to the bootstrap "sr-only" class.

There are numerous problems with the Bootstrap "sr-only" class.

  1. First of all you will see from this discussion that a negative margin can cause issues on VoiceOver.

  2. Secondly you must account for words wrapping one per line as screen readers do not read line breaks

  3. Finally clip has been deprecated.

To fix point 1 simply don't add a negative margin.

To fix point 2 add white-space: no-wrap to ensure words do not end up 'one per line' and cause words to get smushed together.

To fix point 3 we add clip-path: inset(50%) as this clips to a 0px square, we keep clip as at the moment this has great coverage, clip-path is used to future-proof your solution.

Please find below a much more robust class, as of yet I have not found a screen reader / browser combo that does not work as expected with this.

I have this class on a few different forums being tested, so far so good but if someone can find a problem with it please let me know as I will be submitting it everywhere.

.visually-hidden { 
    border: 0;
    padding: 0;
    margin: 0;
    position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
    clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
    clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
    white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
<p>visible text <span class="visually-hidden">hidden text</span></p>
Community
  • 1
  • 1
GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64
  • 2
    There are many variations of this class across the web, and information is scattered (this pull request, that Medium blog post, etc.). It is not easy to find why some alterations were made, nor to know what you should use ultimately. This answer does a good job explaining some common alterations (at least an explanation for the `white-space: nowrap` hack). Do you think this code and explanation could make it to [the reference page from WebAIM](https://webaim.org/techniques/css/invisiblecontent/)? – Maëlan May 08 '21 at 11:56
  • Since you care to remove the border that may have been added by some other rule, maybe you should also remove outline and shadow? `outline: none; box-shadow: none;` – Maëlan May 08 '21 at 12:05
  • Why are we using `rect(1px, 1px, 1px, 1px)` instead of the simpler `rect(0, 0, 0, 0)`? Is it because some screen reader try to be smart and have special treatment for the latter? – Maëlan May 08 '21 at 12:50
  • 1
    Not a bad suggestion on `box-shadow`, I will have to test that (now the fun part of finding an old IE browser instance again!). The exact answer I can't remember, but it was to do with scroll bars or focus indicators and the way a single pixel was rendered. So if we have 0,0,0,0 it is top left and you can see it, something to do with the rendering engine meant things rendered from the top left, 1,1,1,1 is off to the bottom right, so you don't see it as if it did try to render something it starts at 1,1 - maybe someone can give a more accurate / detailed explanation as it has been a while! – GrahamTheDev May 08 '21 at 13:02
  • I think you made a good point about detailing why each step is there, like you said some of the steps are explained in one article, some in another. I might have a crack at answering those couple of questions for you and turn it into a full article to try and settle this once and for all (the typical issue with anything accessibility related - the information is there but in 25 different places!). Not sure whether WebAIM would rewrite / edit their article, not familiar with how they write / create content! – GrahamTheDev May 08 '21 at 13:04
  • Meanwhile, I found other explanations by [WordPress](https://make.wordpress.org/accessibility/handbook/markup/the-css-class-screen-reader-text/). Instead of `white-space: nowrap;` (don’t soft-wrap at all), they use `word-wrap: normal;` (don’t break inside words), but I think the first option is better (the second solves the issue where words are spelled letter by letter, but it does not solve the issue you mention, where line breaks are ignored and words are spelled as only one agglutinated word). – Maëlan May 08 '21 at 13:38
2

I did encounter this problem in the past. Bootstrap has this sweet class sr-only that actually hides the content on the webpage but is accessible by the screen readers. You can check this link

Moreover, if you are not using bootstrap, you can simply implement the class yourself in your code.

.aria-invisible {
      border: 0; 
      clip: rect(0 0 0 0); 
      height: 1px;  
      margin: -1px;
      overflow: hidden;
      padding: 0;
      position: absolute;
      width: 1px;
    }
<span class="aria-invisible">5 selections have been made. </span>

I hope this helps.

ShellZero
  • 4,415
  • 12
  • 38
  • 56
  • Awesome. Thank you so much for your quick response. I have been trying to find this solution for a while now. It just works :) – Donut May 30 '20 at 18:47
1

Using aria-label attributes is the way to do (example below)

Is there a more elegant way to achieve this?

Do not hide the element. Yes. I am not answering your question, but I am addressing the problem.

Screenreaders are only a subpart of assistive technologies used by a small part of people targeted by accessibility guidelines.

Imagine using a screen magnifier for instance where you do not have a global view on the full screen. Imagine having some cognitive disabilities which makes difficult for you to count or remember elements.

If you do consider that an information is important for blind people, then it's surely is for them AND for other people.

Now, instead of it being a long text, it can be a small counter with appropriate aria labelling:

<div role="status" aria-live="polite" aria-label="5 selections have been made.">
  5 selections
</div>
Adam
  • 17,838
  • 32
  • 54
  • 1
    I am almost certain an area marked as `aria-live` will only read the contents and ignore any `aria-label` updates (I have it my company guidance not to use them together, however I didn't link to a fiddle so I cannot remember why, but I only write stuff like that if there is a reason!). You would probably still have to use visually hidden text here. I do agree with the sentiment that the information is useful to everyone and should probably be available visually as well though! – GrahamTheDev May 31 '20 at 08:56
  • perhaps tweak the example to have `5 selections have been made` and remove the `aria-label`, will +1 then as this is good guidance. – GrahamTheDev May 31 '20 at 08:59
  • @GrahamRitchie `aria-live` is a generic attribute without any implied default ARIA role semantic. So you have to explicitely set a role which supports "name from: author" like the ["status" role](https://www.w3.org/TR/wai-aria/#status) which also does not support "name from: contents", implying that `aria-label` is here mandatory. – Adam May 31 '20 at 20:54
  • (and that any content inside should be ignored) – Adam May 31 '20 at 20:59
  • if you add an `aria-label` though you might be right the contents is ignored, but the **update** on that label will also be ignored, rendering your `aria-live` region pointless. I have tried to find the official docs on this and failed but I have found this answer on SO that mentions the behaviour https://stackoverflow.com/a/52432767/2702894. You could use the `aria-label` on a paragraph *within* the `aria-live` region but using as an attribute on the region itself will mean it is not announced. `role="status"` automatically sets `aria-live="polite"` so it does indeed read contents. – GrahamTheDev Jun 01 '20 at 07:29
  • I will try and put a fiddle together for you later that shows this behaviour. Pretty sure it is across most screen readers so it should be easy to test. – GrahamTheDev Jun 01 '20 at 07:31
  • It's the reason why you must set an appropriate role where "name from author" exists for the accessible name computation like all the live region roles : [alert](https://www.w3.org/TR/wai-aria/#alert) [log](https://www.w3.org/TR/wai-aria/#log) [marquee](https://www.w3.org/TR/wai-aria/#marquee) [status](https://www.w3.org/TR/wai-aria/#status) [timer](https://www.w3.org/TR/wai-aria/#timer). Accessible name computation is performed by browsers and AT ignore how the accessible name is calculated. – Adam Jun 01 '20 at 09:50
  • I am not following you at all and I am not sure what point you are making as I was talking about adding an `aria-label` which will not be announced if placed on the **container itself**. But as for setting an appropriate role, it is not needed on an alert, https://www.w3.org/TR/WCAG20-TECHS/ARIA19.html <-- you will see in the WCAG example there is no `aria-live` set as it is *implicit*. This is also specified in each of the examples you linked. What are you attempting to say as I am obviously not following you and I think we are talking at cross purposes here :-P – GrahamTheDev Jun 01 '20 at 10:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215096/discussion-between-adam-and-graham-ritchie). – Adam Jun 01 '20 at 12:37
0

I had the same problem with the text being out of position with the visually hidden class mentioned above. Some small changes to the class fixed this issue for me

.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: auto;
  margin: 0;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
  white-space: nowrap;
}