Preparing your App for VoiceOver: Headers and Heading Level
Learn how to properly use headers and heading levels to enhance the accessibility of your application.
Headers play a crucial role in visually organizing information within the user interface. They act as guides, helping users navigate content more effectively by quickly identifying different sections. This enhances the browsing experience and allows users to focus on the most relevant or interesting parts of the interface.
For VoiceOver users headers are fundamental as they facilitate seamless navigation with vertical swipes. For example, one useful option in the VoiceOver rotor is the ability to navigate through headings by swiping up to move to the previous header and swiping down to jump directly to the next one.
In both UIKit and SwiftUI, we can mark elements of the user interface as headers by applying the header trait. Traits are used to define how assistive technologies recognize an element.
UIKit
In UIKit, marking a label as a header is straightforward. You can achieve this by adding the .header
trait to an element with the .accessibilityTraits.insert()
method. This simple addition ensures that VoiceOver recognizes it as a header.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label.accessibilityTraits.insert(.header)
}
}
SwiftUI
In SwiftUI we can create a more structured and organized heading hierarchy. Using the accessibilityAddTraits(_:)
modifier we can mark an element as a header with the predefined isHeader
trait value.
Additionally, we can specify the heading level of the element ranging from level 1 to level 6 by using the accessibilityHeading(_:)
modifier.
import SwiftUI
struct HeadingsView: View {
var body: some View {
VStack{
Text("Hello Accessibility")
.accessibilityAddTraits(.isHeader)
.accessibilityHeading(.h1)
}
}
}
The .accessibilityHeading(_:)
modifier takes as input an object of type AccessibilityHeadingLevel
. When you do not need to establish a heading hierarchy you can pass the .unspecified
parameter to the modifier.
In the example below you can see how hierarchy was defined in the content:
import SwiftUI
struct HeadingsView: View {
var body: some View {
NavigationStack{
VStack(alignment: .leading){
/// H2 Header
Text("A simple list of elements")
.accessibilityAddTraits(.isHeader)
.accessibilityHeading(.h2)
.padding()
/// H3 Header for each section
List {
Section(header: Text("Animals")) {
Text("Dog")
Text("Cat")
Text("Bird")
}
.accessibilityHeading(.h3)
Section(header: Text("Fruits")) {
Text("Apple")
Text("Banana")
Text("Orange")
}
.accessibilityHeading(.h3)
Section(header: Text("Colors")) {
Text("Red")
Text("Green")
Text("Blue")
}
.accessibilityHeading(.h3)
}
.listStyle(GroupedListStyle())
/// H1 Header
.navigationTitle("Headings")
}
}
}
}
In this case, we are using the navigationTitle(_:)
modifier that already is recognized as a primary header by the system and will be the first element highlighted by VoiceOver.
The text below is marked as a secondary heading using the combination of the two modifiers.
Then we have a list composed of three sections. Having the heading level defined allows users to directly access the desired section, such as "Colors", through vertical swipes without having to go through the inner elements of the previous sections.
This seamless navigation is facilitated by the Section
view's built-in header trait but to enhance clarity and comprehension of the content, we simply added the appropriate heading levels.
When to use Header
You should use at least one header on each screen of your app making it the first element highlighted by VoiceOver. This will provide your users with enough information to orient themselves. By implementing headers to mark each different section of an app users can easily find the content they are searching for.
navigationTitle
and sections within lists, come with a built-in header trait, so you don’t need to explicitly mark them as headers.When to not use Header
Adding too many headers can overwhelm users. It's crucial to ensure that each header should have a clear role, helping users navigate easily through the app.
By keeping things simple, we can ensure users have a smooth and intuitive experience without feeling overwhelmed by too much information at once.
Conclusion
By using the header trait, we ensure that VoiceOver recognizes the element as a header while providing additional context about its hierarchical importance within the content structure.
This level of detail enhances accessibility and enables VoiceOver users to navigate through the interface with greater precision and understanding without endless swipes.