Accessibility: VoiceOver and TalkBack Support
Whistl is designed for everyone, including users with disabilities. This comprehensive guide explains VoiceOver and TalkBack implementation, dynamic type support, colour contrast, switch control, and how Whistl meets WCAG 2.1 AA accessibility standards.
Why Accessibility Matters
Financial protection should be available to all users:
- Visual impairments: 2.2 billion people have vision impairment (WHO)
- Motor impairments: Difficulty with precise touch gestures
- Hearing impairments: Need visual alternatives to audio
- Cognitive differences: Clear, simple interfaces help everyone
- Legal compliance: Accessibility is required by law in many regions
Whistl is committed to making financial protection accessible to everyone.
VoiceOver (iOS)
VoiceOver reads screen content aloud for blind and low-vision users:
Accessibility Labels
import SwiftUI
struct TransactionRow: View {
let transaction: Transaction
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(transaction.merchant)
.font(.headline)
Text(transaction.category)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Text(transaction.amount, format: .currency(code: "AUD"))
.font(.body)
.foregroundColor(transaction.amount < 0 ? .red : .green)
}
.padding()
.accessibilityElement(children: .combine)
.accessibilityLabel(
"\(transaction.merchant), \(transaction.category), " +
"\(transaction.amount, format: .currency(code: "AUD"))"
)
.accessibilityHint("Double-tap to view transaction details")
}
}
Accessibility Traits
struct ProtectedBalanceView: View {
let balance: Double
let isProtected: Bool
var body: some View {
VStack {
Text("Protected Balance")
.font(.headline)
Text(balance, format: .currency(code: "AUD"))
.font(.largeTitle)
.fontWeight(.bold)
if isProtected {
Image(systemName: "shield.fill")
.foregroundColor(.green)
}
}
.padding()
.accessibilityElement(children: .combine)
.accessibilityLabel("Protected Balance: \(balance, format: .currency(code: "AUD"))")
.accessibilityHint(isProtected ? "Your balance is protected" : "")
.accessibilityAddTraits(isProtected ? .updatesFrequently : .none)
}
}
Custom Actions
struct InterventionCard: View {
let riskLevel: RiskLevel
var body: some View {
VStack {
Text("Risk Level: \(riskLevel.description)")
Button("Call Sponsor") {
callSponsor()
}
Button("Start Breathing") {
startBreathingExercise()
}
}
.accessibilityElement(children: .contain)
.accessibilityActions {
Action(named: "Call Sponsor") {
callSponsor()
return true
}
Action(named: "Start Breathing Exercise") {
startBreathingExercise()
return true
}
}
}
}
TalkBack (Android)
TalkBack provides similar functionality on Android:
Content Descriptions
import androidx.compose.foundation.clickable
import androidx.compose.ui.semantics.*
@Composable
fun TransactionRow(transaction: Transaction) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onTransactionClick(transaction) }
.semantics {
contentDescription =
"${transaction.merchant}, ${transaction.category}, " +
"${transaction.amount} dollars"
onClick {
onTransactionClick(transaction)
true
}
onClickLabel = "View transaction details"
}
) {
Column {
Text(transaction.merchant)
Text(transaction.category)
}
Spacer(Modifier.weight(1f))
Text(transaction.amount.toString())
}
}
Custom Actions (Compose)
@Composable
fun GoalCard(goal: Goal) {
Box(
modifier = Modifier
.fillMaxWidth()
.semantics {
contentDescription =
"Goal: ${goal.name}, " +
"${goal.currentAmount} of ${goal.targetAmount} saved"
customActions = listOf(
CustomAccessibilityAction(
label = "Add to goal",
action = {
showAddToGoalDialog(goal)
true
}
),
CustomAccessibilityAction(
label = "Edit goal",
action = {
navigateToEditGoal(goal)
true
}
)
)
}
) {
// Goal card content
}
}
Dynamic Type Support
Text scales with user's system font size preferences:
iOS Dynamic Type
struct ContentView: View {
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
VStack {
Text("Welcome to Whistl")
.font(.title)
.fontWeight(.bold)
Text("Your financial protection companion")
.font(.body)
Button("Get Started") {
// Action
}
.font(dynamicTypeSize >= .accessibility1 ? .headline : .body)
.padding(dynamicTypeSize >= .accessibility1 ? 20 : 12)
}
.padding()
}
}
Android Scalable Text
@Composable
fun WelcomeScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Welcome to Whistl",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "Your financial protection companion",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(bottom = 24.dp)
)
Button(
onClick = { onGetStartedClick() },
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 48.dp) // Minimum touch target
) {
Text("Get Started")
}
}
}
Colour Contrast
Whistl meets WCAG AA contrast requirements (4.5:1 for normal text):
Contrast Ratios
| Element | Foreground | Background | Ratio |
|---|---|---|---|
| Body Text | #171717 | #FFFFFF | 16.1:1 |
| Secondary Text | #737373 | #FFFFFF | 5.0:1 |
| Links | #4F46E5 | #FFFFFF | 5.8:1 |
| Buttons | #FFFFFF | #4F46E5 | 5.8:1 |
| Error Messages | #DC2626 | #FFFFFF | 5.9:1 |
| Success Messages | #16A34A | #FFFFFF | 4.6:1 |
Dark Mode Support
// SwiftUI Dark Mode
struct ContentView: View {
var body: some View {
Text("Welcome")
.foregroundColor(.primary) // Adapts to light/dark
Text("Subtitle")
.foregroundColor(.secondary) // Adapts to light/dark
Button("Action") {
// Action
}
.buttonStyle(.borderedProminent) // Adapts to light/dark
}
}
// Jetpack Compose Dark Mode
@Composable
fun WelcomeText() {
Text(
"Welcome",
color = MaterialTheme.colorScheme.onBackground
)
Text(
"Subtitle",
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Touch Target Sizes
All interactive elements meet minimum size requirements:
Minimum Touch Targets
| Platform | Minimum Size | Recommended |
|---|---|---|
| iOS | 44x44 points | 48x48 points |
| Android | 48x48 dp | 56x56 dp |
| WCAG | 44x44 CSS pixels | — |
Implementation
// SwiftUI - Minimum touch target
Button("Delete") {
deleteItem()
}
.frame(minWidth: 44, minHeight: 44)
// Jetpack Compose - Minimum touch target
IconButton(
onClick = { deleteItem() },
modifier = Modifier.size(48.dp)
) {
Icon(Icons.Default.Delete, contentDescription = "Delete")
}
Switch Control
Users with motor impairments can navigate with switches:
Focus Order
struct OnboardingView: View {
var body: some View {
VStack {
Text("Welcome to Whistl")
.accessibilitySortPriority(1)
Image("onboarding-illustration")
.accessibilityHidden(true) // Decorative
Text("Let's set up your protection")
.accessibilitySortPriority(2)
Button("Continue") {
// Action
}
.accessibilitySortPriority(3)
Button("Skip") {
// Action
}
.accessibilitySortPriority(4)
}
}
}
Reduced Motion
Animations respect system reduced motion settings:
Motion Adaptation
struct AnimatedTransition: View {
@Environment(\.accessibilityReduceMotion) var reduceMotion
@State private var isVisible = false
var body: some View {
Content()
.opacity(isVisible ? 1 : 0)
.scaleEffect(isVisible ? 1 : 0.9)
.animation(
reduceMotion ? .none : .spring(duration: 0.5),
value: isVisible
)
.onAppear {
isVisible = true
}
}
}
Screen Reader Testing
Whistl is tested with actual screen reader users:
Testing Checklist
- Navigation: Can navigate all screens with VoiceOver/TalkBack
- Labels: All elements have meaningful labels
- Hints: Interactive elements have action hints
- Focus order: Logical reading order
- Dynamic type: Text scales correctly at all sizes
- Contrast: All text meets contrast requirements
- Touch targets: All buttons meet minimum size
- Forms: Labels associated with inputs
- Errors: Error messages announced to screen readers
- Live regions: Dynamic content changes announced
Accessibility Features Summary
| Feature | iOS | Android |
|---|---|---|
| Screen Reader | VoiceOver | TalkBack |
| Dynamic Type | Dynamic Type | Font Scale |
| Reduce Motion | Reduce Motion | Remove Animations |
| Invert Colours | Smart Invert | Colour Inversion |
| Switch Control | Switch Control | Switch Access |
| Voice Control | Voice Control | Voice Access |
| Display Accommodations | Display & Text Size | Visibility Enhancements |
Conclusion
Whistl is committed to accessibility for all users. Through VoiceOver and TalkBack support, dynamic type, colour contrast, and switch control, financial protection is available to everyone regardless of ability.
Accessibility isn't an afterthought—it's built into Whistl from the ground up.
Accessible Protection for Everyone
Whistl meets WCAG 2.1 AA accessibility standards. Download free and experience inclusive financial protection.
Download Whistl FreeRelated: Multi-Language Localization | Crash Reporting | Privacy-Compliant Analytics