Accessibility, Tech, Web

VoiceOver and list-style-type: none

VoiceOver and list-style-type-none

Update 2019:This article made it’s rounds again via Twitter, thanks to a Twitter thread by Roger Johannson, Sara Souedian, and Scott O’Hara. After much back and forth a new technique has been found, and Scott has posted Fixing Lists. I also found out that Johnathan Neal has created an interesting postCSS plugin to address this. Based on the Twitter conversation, there is no definite answer as to whether which fix works best or if the fix is even still needed. My advice, as always, TEST! TEST! TEST!

Update 2017: Thanks to @HugoGiraudel and @gumnos for pointing out that I can use zero-width space would be a cleaner solution. I have updated the code below to reflect this.

A bug that keeps hitting me at work is VoiceOver not announcing unordered lists properly when either the list-style-type is set to none or display is set to inline.

The expected behavior is that the unordered list is announced with a role of list along with the number of items, for example “list 4 items.” Lastly, “end of list” is announced after the last item to again let a user know that there are no more items in the list. For whatever reason, setting the list-style-type to none or setting display to inline on the <li>’s will strip this useful information.

This is problematic, for me, because unordered lists are usually used for navigational items, like top-level navigation or side navigation. With most top-level nav, display: inline; is used for presenting a horizontal list of items, while list-style-type: none; is used to remove the bullets that render by default so you can have stylized list of links. You do this a lot, you know you do.

This is kind of a big deal to so I wanted to see if I could figure out a way to get it working right.

ARIA to the rescue?

An obvious solution is to use ARIA roles list and listitem to restore the semantics.

This definitely works, but I don’t like it. It requires too much markup. I also feel that since CSS caused the issue, I should be able to solve it with CSS.

CSS, the real hero

It didn’t take long before I came up with the following solution. I haven’t spent any time trying to figure out why it works yet, which means that I also don’t understand what other consequences there are.

For whatever reason, adding some pseudo content before each item will magically return list semantics in VoiceOver.

With that bit of additional CSS, we are able to return the list semantics for VoiceOver users without needing to touch the markup.


One of the best things about this simple fix is that it does not impact JAWS or NVDA, at least with the versions I have tested:

  • Safari 10.1.2 and VoiceOver/ macOS 10.11.6
  • Safari 10.1.2 and VoiceOver/ macOS 10.12.6
  • Safari 11.0 and VoiceOver/ macOS 10.12.6
  • Safari Technology Preview Release 40 (11.1) and VoiceOver/ macOS 10.12.6
  • Firefox 52.3.0 and NVDA 2015.4 (Thanks Shell!)
  • IE 11 and JAWS 18.0.2324 (Thanks Richard!)

Item Position?

I was surprised that the JAWS and NVDA was not announcing the position of each item in the list by default, e.g. 1 of 4, 2 of 4, etc. The two testers I talked to said they are not used to hearing that information. VoiceOver 10.11.6 didn’t announce either, but 10.12.6 did. I am sure there is some verbosity settings at play here. If you really need, you could always use aria-setsize and/ or aria-posinset. In any case, I was satisfied knowing that the fixed list announced the same as the default list.


Adding some pseudo-content before the list item will add the list semantics back to the list, in the most simple case it could be some zero-width space. It could be any pseudo-content, but it definitely needs to be before, not after. Just make sure whatever you add does not get in the way of interacting with the list item.

Previous ArticleNext Article


    1. Hi Cam, this is definitely only a Safari issue. The use of role=”listitem” is to stay inline with the requirement that role=”list” needs to own children with role=”listitem” but you are right in that it does not effect the end experience. Better safe than sorry!

  1. This was very interesting! Thanks for writing about this. I wonder if using flexbox for lists would be another solution. Or would it still present the same problem in VO? (Thinking out loud by someone who still religiously uses the technique you wrote about.)

    1. Hi Amy, I did test with flexbox, which will definitely inline the items but won’t remove the bullets so everything is intact. Once you remove the bullets though, you are back in the same boat.

  2. Have you submitted this as a bug to the Safari team? (and/or IE and Firefox teams)

    Since some designs won’t require pseudo content on list items, this is a hack that shouldn’t be required for accessibility. I can see this confusing developers over time when it comes up. (though, thank you for the explanation and fix!)

    I hope the browser makers can fix this (or explain why it is) so that we don’t have to include this.

    1. Hi Brad, this only occurs on VoiceOver/ Safari and has been logged many times before. As far as I can tell, this is by design for whatever reason. It’s definitely a hack, but that’s not a bad thing IMO if we can improve the experience for end-users, especially when considering all the polyfills/ normalizations we do for browsers and JS inconsistencies. Thank you for the comment!

    1. Matija, thanks for the question. From what I remember, yes any non-empty content worked. Using anything else created spacing issues that you need to work around. Sometimes absolutely positioning works, but that’s a lot of work IMO. HTH.

Leave a Reply

Your email address will not be published. Required fields are marked *