Traits of a good accessible iOS app

Wed, Jan 20, 2021 8-minute read

Note: This article was originally published in Medium

You may know that you can configure a UI component with an accessibility label. The accessibility label is the name of the component. You can also configure an accessibility trait. The accessibility trait is the role of the component, it gives the user information on how they can interact with it. When using VoiceOver, the trait is usually vocalised after the accessibility label.

Every UI control in your app has an accessibilityTraits property (which is part of the UIAccessibility protocol). Under the hood, it is a bitmask that defines which ones of the different accessibility traits available better describe the UI component. Bear with me, it is easier than it sounds.

At the time of writing this post, there are 17 different traits that you can use to define your UI controls. Button, Selected, Not Enabled, Adjustable… are some examples. You can find the full list in Apple’s documentation. But sometimes, it might not be straightforward to understand what these traits really mean, or how they affect the user experience, in the documentation so I thought it would be a good idea to write a bit about some of them.

You can combine more than one of these traits to define a single component. And for convenience when operating with accessibility traits in Swift, the property conforms to the OptionalSet protocol that conforms to the SetAlgebra one. That means you can use operations like: insert and remove to change the traits that define a component.

It might be easy to see why you would insert, or add, a trait. But what about removing them? Why would you do that? We’ll see an example with the ‘Selected’ trait.

And before we start, I think it is a good moment to mention that if you use UIKit components, a lot of times things will just work and no extra accessibility traits need to be configured. So please, rely on native UI components as much as you can.

Button

It lets the user know that, when the component has VoiceOver’s focus, they can interact with it by doing a double tap anywhere on the screen. And it also tells Switch Control that it is an interactive component. A UIButton has the button trait by default, so why would you ever need to give the button trait to a control?

One of the most common examples where the button trait is often missed is for some cells in table/collection views. Cells are, a lot of times, ‘buttons’ that trigger an action, like playing some music, or bringing the user to a different screen in your app.

cell.accessibilityTraits.insert(.button)

Two examples: 1.Podcasts in a Collection View (Grid). 2.Tracks in Table View. Both have the button trait configured.

But no need to add the button trait if the cell has the disclosure indicator accessory type, though. In that case, iOS will add the trait for you… magic!

Settings screen. Cells have the disclosure indicator accessory type. No need to add the accessibility button trait.

There are also some apps that create custom button components from plain UIViews. One reason why they might do that is to add animations to the button, for when it is tapped.

Probably one of the most important ones as it often helps users navigate an app in a more intuitive and faster way.

Visually, headers help to quickly identify groups of information so we can quickly jump into what it is interesting to us. VoiceOver users can do the same. By using the rotor, you can navigate through headings and jump right to the next/previous heading in the screen with a single swipe down/up on the screen.

To enable the rotor, when VoiceOver on, rotate two fingers on the screen, like trying to turn an invisible knob. The Rotor will appear on screen letting you choose between a number of navigation modes and customisations.

Drawing of how the VoiceOver Rotor looks when selecting navigation through Headings.

Imagine you are using VoiceOver to navigate through the screen represented in the picture. There is a topic cloud, with a header that says “Podcasts”, with 11 items on it. The next topic cloud is for Artists. If no headers are configured, the user will have to do 12 flicks to the right to get to artists. With headers configured, the user will get there with a single swipe down.

Screen with Podcasts heading and 11 topics followed by Artists heading  with 2 topics. 12 swipes to the right or 1 swipe down.

And for letting the user navigate through the headings in your app, all you have to do is to insert the .header trait to any label or view that represents a heading in your app. That’s what I call a quick win!

This one can be a bit confusing, especially if you come from the web development world where there is a clear distinction between what a button and a link are, which doesn’t always seem to apply on iOS apps. The button trait is usually configured for something that triggers an action but also for something that brings the user somewhere else in your app. A link trait usually applies for something that opens some web content and it usually appears in-line in the content and represented by underlined text.

Search Field

It differs from a text field because it will not just let the user know that they can type some text in, but also that it will probably trigger some changes, showing new results as they type.

Adjustable

For UI components that can change a value they’re holding. A good example is a rating UI component, its value can be adjusted to 1 thumb up, 2 thumbs up… 5 thumbs up. It is sometimes used in carrousels too, so you can swipe up/down to select one of the items in the carrousel and swipe right to jump to the next element on the screen. Most of times I think it is a better solution for carrousels to have headings for that purpose though. There is a good blog post about the topic, by Hannah Billingsley-Dadd, in the BBC’s Design & Engineering Medium publication.

Custom rating component. From 1 to 5 thumbs up. Swipe up to increase rating and swipe down to decrease it.

Adjustable is also a bit special, because just configuring the trait may not be enough. There is an extra step needed for it to work properly, you need to override and implement the accessibilityIncrement() and accessibilityDecrement() functions to specify what the behaviour should be when the user interacts with the component by swiping up/down to increment/decrement the value of the component.

override func accessibilityIncrement() {
  guard rating < maxRating else { return }
  rating = rating + 1
  accessibilityValue = "\(rating)"
  sendActions(for: .valueChanged)
}
    
override func accessibilityDecrement() {
  guard rating > 1 else { return }
  rating = rating - 1
  accessibilityValue = "\(rating)"
  sendActions(for: .valueChanged)
}

UISliders are adjustable by default but you can use accessibilityIncrement() and accessibilityDecrement() to specify how much the value of the slider should increment or decrement. Imagine a slider that goes from 0 to 1000. You probably don’t want to increment it 1 by 1, and 100 by 100 might be more reasonable.

Selected

There are elements that can be selected or not. Cells in table views have the possibility to be selected, for example. In those cases you can use the selected trait to let the user know that the element is selected. It is often used for buttons that act as toggles and that can have an on or off state.

An unfilled like button has the button trait. A filled like button has the button and selected traits.

But remember, if it gets unselected, you need to remove the selected trait too.

if likeButton.isSelected {
  accessibilityTraits.insert(.selected)
} else {
  accessibilityTraits.remove(.selected)
}

Updates Frequently

This one is useful for views that change its label, or value, and you want to update the user while the focus is on the component. I think a downloading progress bar is a good example of this. Without this trait, the user would just hear the value the component had when they landed on it. With this trait, every now and then, the accessibility trait will poll for updates and announce any changes to the user.

Drawing of a downloading progress bar at 15%. VoiceOver can update the user at 5% and 10% when using updatesFrequently trait.

None

Not a very interesting one, just useful for resetting the accessibility traits of a component.

aView.accessibilityTraits = .none

And more!

Not enabled: It causes VoiceOver to say “dimmed” after the accessibility label, indicating the user that the component is disabled and that they can’t interact with it at the moment.

Starts media session: If the purpose of a button is to start playing some music, or a video, this trait causes the VoiceOver announcement to stop as soon as you interact with it. Plays sound causes a similar effect.

Keyboard key: can be used for custom keyboard keys, like for a custom pin keyboard with just numbers. Although it stops VoiceOver from saying button, so it might not be desirable.

Allows direct interaction: If you have a control that doesn’t play well with VoiceOver’s gestures, you can allow the user to interact directly with it. I imagine it is something similar to what happens when the user has direct touch typing mode enabled. When that is the case, touching a key in the keyboard causes the key to be activated, instead of needing to first select it and then double tap it.

For Interface Builder lovers

If you use interface builder for building the UI of your apps, you can configure the accessibility traits, like many other accessibility properties in the identity inspector.

Interface Builder menu to configure Accessibility properties such as: label, hint, identifier and traits..

It’s a wrap!

I hope this helps and that you’ve identified in this post some UI controls in your app that could be great candidates to get some of these traits configured. I’d like to hear from you. Would you like to share any examples from your app? Do you think I’ve missed anything important? Was there any accessibility trait in the post that you didn’t know about and may start using now? You can write your comments/questions/feedback in the Medium post or reach me on Twitter with the @dadederk handle. Thanks for reading!

If you are one of the lucky ones using SwiftUI already, you usually have equivalent attributes and modifiers to add and remove traits to an element. To know more, I totally recommend this blog post by Rob Whitaker.

Resources:

Developing Accessible iOS Apps by Daniel Devesa Derksen-Staats

You can find all these tips, and many more in my book, Developing Accessible iOS apps.

Available in:

And some useful accessibility code examples in my book’s repo.