For the last few months, I've been working on a project that uses a combination of NextJS, Storybook, and various other tools to build out a simple eCommerce website. Thankfully, the project remit purposefully included quite a bit of budget for accessibility testing, which meant that we've been performing a number of both automated and manual tests at every stage of development (🎉).
However, from quite early on we began to notice some odd behaviour with screenreaders, and NVDA on Windows in particular. When we'd load a component and navigate via tab order or arrow keys, everything would be fine and dandy, but the moment you used shortcut keys to jump around certain content types, everything would be announced as "clickable" or interactive, even when it clearly wasn't.
To be clear, what I mean by "shortcut keys" are (for example) using the h key to move between heading elements on a page, or the l or i keys to jump around lists. They're a super useful feature of a lot of accessible technologies that allow you to navigate around a web page much quicker, skipping a lot of otherwise extraneous information. In practice, these should provide the same user experience as any other navigation method, but for some reason, when we tested them, they appeared to be turning everything from static images to subtitles to bullet point lists into elements that registered as interactive, but had no interactivity. Not ideal!
After several attempts at getting to the bottom of things, the post-Christmas slump has finally given me some dedicated time to properly dig into the issue, and it turns out to have been irritatingly simple. My first port of call was replication: can I still get the bug to trigger? Well, yes. I load up a Storybook page, open NVDA, and press a shortcut key to jump to a given element. Every single time it would log as "clickable". Next, let's see if something else on the page is somehow passing attributes to my elements? I launched the component out of the standard Storybook UI to its own page, but the issue persists. I removed all other elements from the component (starting with interactive ones, like buttons and toggles) until all I had left was a list of two items, and yet still it registered as "clickable" 😒
Well, since we originally logged this bug we've actually built the final site and kicked off UAT, so I wondered if any of the testing on the live environment had flagged similar issues. Oddly, it hadn't. Perhaps they weren't using NVDA for UAT, though, so I launched a test environment on my Windows PC and navigated to a page using the component I had been examining and... nothing! NVDA just picked up a list, no mention of interactivity, no odd behaviour. I tried every navigation technique I could think of, and they all passed without issue. The hell‽
I started searching around online for any mentions of Storybook/NVDA incompatibilities or bugs and came up empty. However, I did notice one small comment on an old NVDA discussion thread: elements will be announced as "clickable" if they do not explicitly state otherwise and are contained within an interactive parent or a parent with an event listener. That's an interesting addition. I get the first one – after all, many interactive elements (buttons, links, etc.) can have children, and these should also trigger the interaction. But the second was a little less obvious.
Back to the component code I went and found... absolutely nothing. No React callbacks or refs, no event listeners, no hook-based interactions. Not on my list element, not on its parent, not on the root component in the file. But what about the component that Storybook uses to render within? After all, this is React, and React tends to have a nameless
<div> floating somewhere that gets replaced during build or run time with the actual contents of the page. I loaded the component back in Firefox, opened by developer tools, and there it was: a tiny little "event" badge sat next to my root element. I'd checked the
<body> element, I'd checked my code, but I'd forgotten that Storybook would also inject a "container" element of some kind. Expanding the badge showed that Storybook seems to add every possible event listener to the root element, including (of course) click and tap events. (I imagine this is so that events can be passed around the iframe view as needed, but I'm not really sure; maybe it's something that React does itself, just to make its own interactivity possible?)
At any rate, this would be my top tip. If you're getting unexpected "clickable" announcements from assistive tech, or any other kind of flag that seems to indicate that static content is somehow interactive, pop open your developer tools and have a scan around for rogue event listeners. It turns out that they bleed a little (a lot, in this case) and can cause some odd behaviours. The more you know 🌠