JUCE Accessibility on `develop`

This is all great stuff - thanks for the detailed reports. We’ll be going through all of these over the next few weeks so please keep posting any issues you find.

All my widgets have tooltips, is there a way to make this available in voiceover ?

1 Like

Regarding customizability of the AccessibilityHandlers, if I want a custom handler for my components (for example let’s say I want to display the component tooltip in the AccessibilityHandler::getHelp() method, or maybe I want to return something different than getButtonText in ButtonAccessibilityHandler::getTitle()), I have to subclass all the components that I want to have this customized handler.

I think quite often we will want to have an easier way to change the default AccessibilityHandler so that we do not have to subclass all Component classes. For example the LookAndFeel class could be used to return a default accessibility handler for the various juce widgets. So the implementation in juce::Button would be:

virtual std::unique_ptr Button::createAccessibilityHandler() {
return LookAndFeel::createButtonAccessibilityHandler();

The same could be applied for FocusTraverser classes, delegating (by default) their creation to LookAndFeel would simplify the replacement of juce-provided FocusTraverser with a custom one.

1 Like

first off, this is awesome and i’m glad to see it.

when this is finally added to ios, will the accessibility features enable ui testing from xcode? right now trying to record interactions fails because none of the juce ui elements are recognized by xcode, but i’m wondering if identifying widgets to screen reader software is essentially solving this problem too?

1 Like

Yes on iOS this will enable Xcode UI testing as it exposes all of the accessible JUCE UI elements to the OS which were previously just drawn inside the window.


that’s great, thanks for the update!

On Windows, popup menus have the same issue I saw with tooltips, where the narrator focus gets stuck on a window that no longer exists.

Any thoughts on a custom component that just displays a value that periodically updates? Like a CPU or voice count meter. The AccessibilityValueInterface seems to only be for numbers. How can I let the screen reader know that value has changed?

Will there be some updates to the accessibility branch soon?

Thanks, I’m looking into the Narrator focus issues at the moment. We’ll be updating the branch in the next few days with lots of fixes/improvements and this fix should be a part of that.

Have you tried posting an AccessibilityEvent::valueChanged event using the notifyAccessibilityEvent() method of the handler which is updating?

1 Like

We’ve just updated the accessibility preview branch to address the issues that have been raised in this thread. Please try out the latest changes and let us know if there are still issues, or if you run into anything new. Thanks everyone for the detailed feedback so far!


Shouldn’t ButtonAccessibilityHandler::getTitle return the title set to a Button when it is not empty, and then only use getButtonText as a fallback ?

There is something that I don’t understand regarding focus order. Let’s say I have component A at {x=0, y=0, width=100, height=100}, and component B at {x=100, y=0, w=100, h=100}.

A contains a sub-component A1 at y=0, and A2 at y=50. B contains B1 at y=0 and B2 at y=50.

If I traverse them using the keyboard focus (with the tab key), I will visit them in that order:
A1 -> A2 -> B1 -> B2 . First the childs of A, and then the childs of B

If I visit them with voiceover, the order will be:
A1 -> B1 -> A2 -> B2.

It seems that the second order is not something decided by JUCE, but something decided by VoiceOver, am I right ? Unfortunately I very much prefer the first order, which seems much more natural. Setting ‘setFocusContainer(true)’ in A and B fixes the order in voiceover, but now the 4 components are no more traversable with the tab key (focus will be A1->A2 or B1->B2 depending on which component is initially focused).

The KeyMappingEditorComponent is not fully accessible (the description labels are not read by VoiceOver), see for example KeyMappingsDemo.h in DemoRunner.

With commit ec99020 we have now added the accessibility changes to the develop branch and have removed the old accessibility branch. Please pull the latest tip of develop where we’ll continue to improve and update the accessibility support over the coming weeks in preparation for JUCE 6.1.

Please continue to use this thread to provide any feedback and, once again, thanks for all the suggestions so far!


Yes that seems more intuitive, we’ve added that.

We’ve updated the KeyMappingsDemo and KeyMappingEditorComponent so they are now accessible.

The navigation order is controlled by the ComponentTraverser object returned by Component::createFocusTraverser(). The default Component implementation returns a FocusTraverser object which uses the following to determine the order of navigation:

    The algorithm used by this class to work out the order of traversal is as
    - Only visible and enabled components are considered focusable.
    - If two components both have an explicit focus order specified then the
      one with the lowest number comes first (see the
      Component::setExplicitFocusOrder() method).
    - Any component with an explicit focus order greater than 0 comes before ones
      that don't have an order specified.
    - Components with their 'always on top' flag set come before those without.
    - Any unspecified components are traversed in a left-to-right, then
      top-to-bottom order.

The hierarchy can then be controlled using the updated Component::setFocusContainerType() method which takes a FocusContainerType enum specifying whether the component is a focus container and whether it should also be a keyboard focus container. This is different to the old setFocusContainer() method which would only set the component to be a keyboard focus container.

The documentation for this enum and new method have much more info so I would suggest reading through them, but in your situation you would now call setFocusContainerType (FocusContainerType::focusContainer); on A1 and B1 and they would act as accessibility focus containers for A2 and B2, respectively, whilst preserving the old tab key focus behaviour.

The hierarchy can then be controlled using the updated Component::setFocusContainerType() method which takes a FocusContainerType enum specifying whether the component is a focus container and whether it should also be a keyboard focus container. This is different to the old setFocusContainer() method which would only set the component to be a keyboard focus container.

I missed that change, thanks ! This will be very useful.

Fantastic to see this moving forward! Is there any chance of putting a redirect in place from the accessibility preview branch, Ed? Asking because there are a bunch of devs I’ve emailed over the past couple of weeks who’re likely going to land on what’s now a dead link.

There’s no way to redirect the old GitHub branch link as far as I’m aware. We’ll be updating the develop branch with the latest accessibility features and bug fixes going forward so needed to remove the accessibility branch to prevent confusion and people using out of date code.

It seems Doxygen got confused on Component::createAccessibilityHandler(), it’s mixed with the stuff before and after.

Thanks, we’ll get that sorted out.

I have a ListBox which uses custom row components. Let’s say the height of the listbox is 10 rows.

I start with the row 0 being selected by voiceover, I can move (with ctrl-option-arrow) to the row 1, row 2 etc up to row 9. When row 10 is selected, the listbox scrolls so that rows 10 to 19 are displayed. When I move to the next row, instead of selecting row 11, it is row 20 that is selected.

I believe this might be caused by the way ListBox is recycling its row components and it messes the focus order, but I’m not sure.

It seems something a bit similar happens in the ‘ValueTreesDemo’ in DemoRunner , when the list start to scroll, the focus jumps over a few rows – but only if we use voiceover (with ctrl+option+arrow).