Accessibility in Qt: It's a minefield!
When building a cross-platform desktop app that plays nice with screen readers, I ran into more problems than I could have imagined right out of the gate. If you ever do something similar, you’ll likely need to work around the bugs in this article. Unfortunately, even if you use a mature UI framework with a good reputation, things will not “just work” in the accessibility world.
When it comes to cross-platform UI development specifically, there aren’t many good accessible options. Screen readers and keyboard navigation often need additional invisible metadata that varies between platforms. Unfortunately, relatively few framework developers have prioritized this.
In my research the only recommended options are either web browser based or Qt.
The project I’m describing today is a productivity app called 3DMake that helps Blind users design and print 3D prints. It’s really a combination of a slicer and an IDE. For this kind of program, many users prefer native desktop apps, and I’m one of them. I appreciate being able to quickly and easily open multiple windows, and I like to save disk space by not having a giant browser engine bundled with every app. I have users on Windows and MacOS, so I naturally need a cross-platform solution.
Qt (pronounced “cute”) is a mature framework that many excellent programs use, and has well documented accessibility support. Its developers have made the effort over the years to build screen reader support into the framework for all platforms.
Despite this, when I actually tested basic interactions with a screen reader, there was broken behavior around every corner. I’m writing to share my experience with other developers who might struggle through the same things.
I was using PySide6 in Python to build a prototype, and testing with both Voiceover on macOS and NVDA on Windows. The issues I’ll discuss aren’t specific to the Python bindings, so if you’re using the C++ API you might run into them as well.
QListWidget
QListWidget is a vertical box containing a list of options, at most one of which can be highlighted. In 3DMake I wanted to use it to select categories of printer settings.
However, I found that in VoiceOver this control doesn’t work properly at all with screen readers. Normally you would want to navigate to the control using the Tab key, at which point the screen reader would announce it by name. Then you’d use the arrow keys to choose the selection within the control, and they would be announced so you knew what was currently selected.
In Qt 6.11.1, this control is not usable at all through VoiceOver. When you tab to it, the name doesn’t get read out and it doesn’t take keyboard focus properly. And when you use the arrow keys to change selections, those selections aren’t announced either. Curiously, the control works reasonably well if I downgrade to Qt 6.9.0. In that version, the list box’s overall UI label isn’t announced in VoiceOver, but the item navigation and keyboard focus works properly. A PySide6 bug describes this exact problem.
QComboBox
A QComboBox is a drop down selection widget. When using a screen reader, this widget should behave like the QListWidget, announcing its label when it takes focus, and the selections as you cycle through them.
Under Qt 6.11.1, the QComboBox announces its label and the selected option when you tab to it, but when changing the option wit the arrow keys it doesn’t announce anything. This means that a user could easily change an option without realizing they had done so. It might perhaps be workable for users used to this kind of broken behavior, because they can tab away from the control and back to determine what they’d selected.
After downgrading to Qt 6.9.0 to fix the above QListWidget issue, the QComboBox also won’t announce its associated label (I.e. the name of the form element) through VoiceOver. So when you select it, you don’t know what you’ve just tabbed to.
I found a Qt bug that seems to describe this issue.
QTreeView
QTreeView is a kind of list box widget that supports an expandable hierarchy where some rows can be expanded to reveal child rows beneath them. I planned to use it as a directory explorer so can explore a 3DMake project’s .scad files in the editor sidebar. This is a very common pattern for IDE and editor applications where you work with multiple files that are part of the same project.
Under Qt 6.11.1, this widget can be focused and navigated, but the focus often doesn’t “keep up” with selected items. If I select a row and hit the down arrow 3 times, it may only read an arbitrary one of the three rows out with VoiceOver.
A more serious problem is that this widget frequently crashes the application when expanding a group in both versions of Qt I tested, but only when VoiceOver is active. I can use the keyboard to expand and contract the TreeView groups to my heart’s content with VoiceOver off, but the moment I enable it there is at least 30% chance that the next group expansion will cause a crash.
This widget can be set to disable manual expansion / contraction of groups, so the whole hierarchy is always visible. For many projects that would be a reasonable compromise.
Remarks
Trying to work around these bugs in a single dialog took a full day, even with help from Claude Code. In the end, I reluctantly switched to a web-based UI for my prototype so I can focus on other things. But in my heart, I’d still rather ship a native app, and 3DMake users would prefer one as well.
Despite the provocative title, nothing in this article is meant to disparage Qt. It’s a very complex piece of software, and Qt maintainers have put more work into accessibility than any other cross-platform GUI framework. At the same time, this highlights the sad state of accessibility support in general. If even the most reputable framework has broken support for common controls – widgets we’ve been using since the 80s and 90s – that means writing accessible cross-platform software will be an uphill battle from the start.
If you are doing similar work, my advice would be to budget more time than you expect and test thoroughly on each platform. I also found this blog post by Andrei Tuicu to be very helpful when it comes to understanding Qt accessibility interfaces.
by Troy