I recently built an e-commerce site, and I attempted to build a strong accessible foundation along the way. The project is mostly complete, but I thought it was a great opportunity to comb through the project to add aria labels where they are needed.
I hope this can serve as an introduction to adding aria labels in a React/NextJS project.
Landing Page
On the landing page we have a few items to address. Using Firefox's Accessibility Inspector we can see multiple issues in the header alone.
Burger Menu
First and foremost I want to make sure the burger menu is accessible and makes sense to everyone. Adding an aria-label easily removes our warning.
<button
className={ menuOpen ? "burger-btn active" : "burger-btn"}
onClick={handleMenuToggle}
aria-label="open main menu"
>
But, you may notice that this button is a toggle button, so what happens when our menu is open?
Our label informs our user that this button will "open main menu". But the menu is already open, and visually we are showing a huge X, so this is rubbish.
Using the same ternary logic I used for my button className, I can update the aria-label to change based on the state.
<button
className={ menuOpen ? "burger-btn active" : "burger-btn"}
onClick={handleMenuToggle}
aria-label={ menuOpen ? "close main menu" : "open main menu"}
>
Now our aria label updates with our menu opening and closing. When I use voice-over I hear "open main menu, button" when I apply focus (tab to the button) and when I hit enter, I hear "close main menu" immediately. That worked better than I even though honestly.
Page Header
My site title is "C&G", which doubles as a link to the landing page, though I am not sure you'd never know that from the voice over. What I currently hear is "link, C & G". I'm going to add an aria label to this as well.
<Link href="/">
<a aria-label="c & g home page">C<span>&</span>G</a>
</Link>
Now I hear "link, c & g home page" when I apply focus to the element. If this link element looks strange, just know I'm using NextJS along with React.
Cart Icon
Next up is our cart icon. The code currently reads as such
<button
className="header-cart"
onClick={handleCartPreviewClick}
>
<img className="cart-vector" src="/shopping-cart-vector.png"></img>
{context.contextCart.length > 0 ?
<span className="cart-count"> {context.contextCart.length}</span>
: null }
</button>
I immediately notice some issues. For one, my image tag has no alt attribute. While I'm fixing that, I can add a ternary aria label just like our burger menu button.
<button
className="header-cart"
onClick={handleCartPreviewClick}
aria-label={ cartPreview ? "close shopping cart preview" : "open shopping cart preview" }
>
<img
className="cart-vector"
src="/shopping-cart-vector.png"
alt="shopping cart"
/>
{context.contextCart.length > 0 ?
<span className="cart-count">{context.contextCart.length}</span>
: null }
</button>
That solves the same issues I had with my burger menu toggle button. Although when I use the screen reader I only receive information about the image inside the button, or the span when I am first loading the page. I'm not thrilled about that, I'd like to have the number of cart items be relayed somehow. I am going to try to add that into the button's aria label.
I attempted to interpolate my cart items length into the aria label, but that doesn't work. So I will leave that issue open for now. At least the button instructions make sense now.
Conclusion
Well addressing only the header took a while, so I will have to write another article detailing the slider on the landing page and digging further into our cart items - which I fear aren't laid out too well.
If you'd like to see the page as it stands currently or checkout out the code the links are included below.
Top comments (1)
Wonderful that you are putting effort into accessibility. ❤
I had a quick look at the home page - few suggestions / quick fixes:-
Invalid WAI-ARIA
You have a couple of minor mistakes.
First the
role
on your<nav>
is not valid. Currently it is "main menu". I think you meant "menu".Two problems there. First that isn't the correct
role
in the first place (menu
is for application style menus not navigation) and secondly you have now added the requirement to add arrow key navigation along with other shortcut keys!Just remove the
role
- because you have used a<nav>
element it already has an explicit role of "navigation" - which is the relevantrole
here anyway!edit sorry I just realised this was probably just another minor mistake - was that also meant to be
aria-label="main menu"
? I have left the above in as you may find it interesting but I imagine the fix is to just change it fromrole
toaria-label
😋Second you have an
aria-labelledby
that is pointing to "hide product categories". I think that was meant to be anaria-label
perhaps?What you have essentially done is say "the label for this element is provided by 3 other elements. The ones with the IDs
#hide
,#product
and#categories
, read in that order!That is how
aria-labelledby
works - it wants an ID (or several) of an element on the page that acts as the label.h1
You must have a
<h1>
on the page to help screen reader users orient themselves.Now although this should be visible (to help comprehension and orientation for everybody) if you design really can't accomodate it you can visually hide it with
visually-hidden text
(screen reader only text). You can read about that more and the recommended class properties I suggest in this StackOverflow answer I gave:A better solution to the bootstrap "sr-only" class.
There are numerous problems with the Bootstrap "sr-only" class.
First of all you will see from this discussion that a negative margin can cause issues on VoiceOver.
Secondly you must account for words wrapping one per line as screen readers do not…
To use it simply give the element you want screen reader users to see but nobody else the class
visually-hidden
and include the CSS class I linked:<main>
and skip linksI may have missed it but it looks like you missed a
<main>
landmark. This is important as some people who use a screen reader will jump to this using shortcuts.Along the same lines "skip links" allow people who use a screen reader to jump past the navigation on your site so they don't have to tab past it on every page.
Erroneous
tabindex="-1"
Not sure why but you have a
tabindex="-1"
on your link that says "shop watches".This disables the link and means it cannot receive focus for keyboard users.
If you are trying to focus it programatically you do not need the
tabindex="-1"
so you can remove it and still use.focus()
as it is an element that can receive focus in the first place.That is enough for now!
It is quite a broad subject so I don't want to overwhelm you. But as you can see the fixes themselves are easy, it is just the knowledge that is harder to acquire (but you will get there!).
Any questions on the above just ask and if you get stuck on a coding problem related to accessibility throw it up on StackOverflow with the tag [accessibility] - I am always lurking around there to help!