Designing Mobile Apps for Accessibility

A hands-on guide for mobile developers

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

Principle 1: Support large content sizes

SBB inclusive with regular (left) and accessibility XXXL (right) content size.
// 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('...'),
),
],
)
// 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).
Using ScrollViews to make all content accessible for larger content sizes.
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();
}

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

Color palette used for SBB Inclusive.
// 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.
Light and dark mode using our (very restricted) semantic color palette.

Principle 3: Optimize VoiceOver

“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.”

Hiding unnecessary information in VoiceOver.
// SwiftUI
Image(”train-small”)
.accessibility(hidden: true)
// Flutter
Image.asset(
Images.trainSmall,
excludeFromSemantics: true,
)
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'),

],
),
),
Providing better VoiceOver texts.
// SwiftUI
Group {

}
.accessibility(label: Text(TextFormatter.stopStation(for: stopStation).voiceover))
// Flutter
Semantics(
value: createSemantics(stopStation),
child: …,
)
Using headers for quicker navigation in VoiceOver.
// SwiftUI
Text(”Einstellungen")
.accessibility(addTraits: .isHeader)
// Flutter
Semantics(
header: true,
child: Text('Einstellungen'),
)
Attention to detail in VoiceOver (e.g. using TimeFormatters)
// SwiftUI
Text(trainConnection.departureTime)
.accessibility(value: DateFormatter.hourMinuteVoiceoverTime.string(from: trainConnection.departureTime))
// Flutter
...

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
AppBakery

AppBakery

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