Designing Mobile Apps for Accessibility

A hands-on guide for mobile developers

AppBakery
8 min readAug 21, 2020

It’s not often that you get to write an app specifically targeted at an audience with accessibility in mind. Yet, that’s exactly what we were lucky enough to do this year. In this article you’ll get to know our best-practices for designing accessible mobile apps and how to cleverly implement them, optimizing your precious time as a developer. You’ll also find code snippets for SwiftUI and Flutter.

The idea was to create SBB Inclusive - an app supporting blind people as well as people with poor eyesight in their daily use of the public transport system in Switzerland by providing them all relevant information for their specific location. So if User A is currently sitting in a train to Geneva, the app will provide him information about the train he is currently on. Or if User B is in Zurich right now, we’ll tell him about his next departures from Zurich trainstation. The app is called SBB Inclusive and optimized for large content size as well as VoiceOver usage. It will be available to the public by the end of 2020.

SBB Inclusive (iOS/Android app with accessibility as a main focus).

Developing an app with accessibility as our main focus was an interesting learning process. We’ve learnt a lot from our blind test users and from using accessibility features ourselves on a daily basis. We’d like to share our insights accompanied with examples taken from our app.

Three key principles will guide us through this article:

  • Support large content sizes​
  • Choose colors wisely (& support dark mode)​
  • Optimize VoiceOver​

So without further notice, let’s dive right in!

Principle 1: Support large content sizes

Many users with restricted eyesight will make use of the OS’s ability to increase text size (also called dynamic content size). This makes it vital for you as a developer to support those different content sizes.

SBB inclusive with regular (left) and accessibility XXXL (right) content size.

Make sure your texts aren’t clipped

While testing your app with larger content sizes, one of the first things you’ll notice is that some of your texts are getting clipped. The good news is, that this is easy to fix in most cases.

Depending on the case there are two different solution approaches. First, you can make sure that you allow multiline on your Texts.

// SwiftUI
Text("...")
.fixedSize(horizontal: false, vertical: true)
// Flutter
Text('...'), // it usually just works ¯\_(ツ)_/¯
// but use Expanded for Text in a Row
Row(
children: <Widget>[
Expanded(
child: Text('...'),
),
],
)

However in some usecases (like buttons for example) this might not be desirable. So as a second fix you can also restrict the maximum font size for certain texts, so that the maximum font size is automatically computed by the OS. Bear in mind that the second approach is not optimal for people with restricted eyesight, because it might display some texts in a smaller font than their preferred content size settings. So use it only in some edge-cases and rely on allowing multiline wherever possible.

// SwiftUI
Text("...")
.minimumScaleFactor(0.1)
// Flutter
AutoSizeText( // Use this awesome plugin
'...',
maxLines: 1,
minFontSize: 1,
)
Clipped text (left), text with multiline (center), text with restricted maximum font size (right).

It is important to note that in many cases a larger content size means that you can’t display everything you want on the size of your screen. This is fine for the users as they’re used to it. However for you as a developer, this means that you’ll need to use ScrollViews in places you wouldn’t when designing for regular content size.

Using ScrollViews to make all content accessible for larger content sizes.

Provide alternative layouts for larger content sizes

Unfortunately, even the best (and simplest) designs won’t work for all content sizes. In some cases you’ll need to provide an alternative layout for larger content sizes, where the views arrangement differs according to the content size. Sometimes it even makes sense to hide icons to create more space for more relevant content.

Regular content size (left), accessibility XXXL with same layout (center) and accessibility XXXL with different layout (right).
// SwiftUI
@Environment(\.sizeCategory) var sizeCategory
if SizeCategories.accessibility.contains(sizeCategory) {
TrainConnectionRowAccessibility(…)
} else {
TrainConnectionRowNormal(…)
}
// Flutter
final mq = MediaQuery.of(context);
// Decide for yourself when to switch
if (mq.size.width <= 330 || mq.textScaleFactor >= 1.4) {
TrainConnectionRowAccessibility();
} else {
TrainConnectionRowNormal();
}

So to wrap up our principle 1 (support large content sizes):

  • Make sure your texts aren’t clipped by allowing either multiline (in most cases) or by restricting the maximum font size (in some edge cases). In many cases you will need to use ScrollViews to make all content accessible.
  • Make sure you use the space in an optimal way by implementing alternative view layouts for different content sizes and hiding less relevant content (e.g. icons) in larger content sizes

Principle 2: Choose colors wisely (& support dark mode)

As you might already know, many users will not be able to distinguish the entire color palette. Also with restricted eyesight, strong contrasts become even more important. We learnt that using a small color palette with only few colors works best. Ideally this palette features some foreground and background colors which all have strong contrast ratios to each other.

Color palette used for SBB Inclusive.

Many users with restricted eyesight use dark mode due to it’s better contrast ratio. To make our lifes easier we created a set of semantic colors. What do we mean by ‘semantic color’? A semantic color is a set of two colors with a color value for light and dark mode each. So instead of having to worry about light and dark mode colors, you define those sets once and use them everywhere in your app.

// SwiftUI
Text(“Bern”)
.foregroundColor(SBBColor.textBlack)
// Flutter
Text(
'Bern',
style: Theme.of(context).textTheme.bodyText1
.copyWith(color: SBBColors.textBlack),
);
Semantic Colors used for SBB Inclusive.

With your semantic colors defined it really becomes a pleasure to support both light and dark mode because it doesn’t require any additional effort on the developer side.

Light and dark mode using our (very restricted) semantic color palette.

Principle 3: Optimize VoiceOver

Obviously, when designing an app with accessibility in mind, you need to think about VoiceOver. However, before getting into tips on how to optimize your users VoiceOver experience, here’s our key learning:

“You (as a developer with good eyesight) need to start using VoiceOver on a daily basis. By doing so, you’ll get a feeling for it and understand that the app flow is really essential for a good VoiceOver experience.”

So start by setting up shortcut commands for VoiceOver on your device and use it regularly. The following tips are our learnings from using VoiceOver ourselves and from our blind testers’ feedback .

Hide unrelevant visual information

VoiceOver will try to read out every single view that is displayed. However this is not always helpful. Often an app’s UI makes use of icons or other elements to enhance the meaning of displayed text. VoiceOver users are not interested in this duplicate information, so hide unnecessary information for them.

Hiding unnecessary information in VoiceOver.
// SwiftUI
Image(”train-small”)
.accessibility(hidden: true)
// Flutter
Image.asset(
Images.trainSmall,
excludeFromSemantics: true,
)

Combine information belonging together

Often the real information content only becomes clear, if you assemble single pieces of information. Each single piece of information itself doesn’t yield any relevant information. This means that you need to combine those pieces of information for VoiceOver.

Combining information that belongs together.
// SwiftUI
Group {
Text("12:25")
Text("12:26")

}
.accessibilityElement(children: .combine)// Flutter
MergeSemantics(
child: Column(
children: const <Widget>[
Text('12:25'),
Text('12:26'),

],
),
),

Provide better VoiceOver texts

Clever UI design provides context to presented views without explicitly stating it. Take a look at the following screens. As someone with good eyesight, you’ll automatically assume, that the train arrives at 12:25 and departs at 12:26. However this information is not explicitly written down using texts. VoiceOver users are lacking those visual associations, so you need to add it to the texts being read to provide meaningful information.

Providing better VoiceOver texts.

Unfortunately this can’t simply be done by adding a specific attribute to your code. We propose to implement a custom TextFormatter responsible to provide meaningful VoiceOver texts.

// SwiftUI
Group {

}
.accessibility(label: Text(TextFormatter.stopStation(for: stopStation).voiceover))
// Flutter
Semantics(
value: createSemantics(stopStation),
child: …,
)

Use headers

To reduce drag gestures (used to “swipe” from element to element in VoiceOver), many VoiceOver users will navigate from header to header for quick access to their content of interest. To help them to so, you need to specify which of your views are headers.

Using headers for quicker navigation in VoiceOver.
// SwiftUI
Text(”Einstellungen")
.accessibility(addTraits: .isHeader)
// Flutter
Semantics(
header: true,
child: Text('Einstellungen'),
)

Attention to detail

While the VoiceOver feature is really powerful on both iOS and Android, it’s far from perfect. So you’ll have to help it out a little in some specific cases. Here’s an example:

Attention to detail in VoiceOver (e.g. using TimeFormatters)
// SwiftUI
Text(trainConnection.departureTime)
.accessibility(value: DateFormatter.hourMinuteVoiceoverTime.string(from: trainConnection.departureTime))
// Flutter
...

To detect those cases where VoiceOver is not perfect you’ll need to test your app over and over again. Again, we can’t emphasize enough how important it is to use VoiceOver on a regular basis.

Wrapping up our third principle (optimize VoiceOver):

  • Start using VoiceOver on a daily basis to get a feeling for it
  • Hide unrelevant visual information (e.g. icons)
  • Combine information belonging together
  • Provide better VoiceOver texts
  • Use headers
  • Attention to detail
  • Test VoiceOver (again and again)

Conclusion

You’ll notice that once you get the hang of it, designing for accessibility isn’t really hard. Once you are familiar with the key principles, it won’t take a lot of your precious time as a developer. On the other hand your app will be suitable to a bigger audience and more importantly accessible to everyone. Our biggest learning however is that when designing for accessibility, the general user experience of your app will also get a boost because your app’s flow will get simpler.

Further reading:

--

--

AppBakery
AppBakery

Written by AppBakery

In-house agency for Mobile Apps & Web at Swiss Federal Railways #sbbcffffs

No responses yet