Skip to content

Commit ffaa12c

Browse files
[Mobile] Complete Accessibility Audit (#269)
* [jules] a11y: Complete accessibility labels for all mobile screens Co-authored-by: Devasy <110348311+Devasy@users.noreply.github.com> * [jules] a11y: Add accessibility hints for destructive actions Co-authored-by: Devasy <110348311+Devasy@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 637cbb6 commit ffaa12c

14 files changed

Lines changed: 142 additions & 7 deletions

.Jules/changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
## [Unreleased]
88

99
### Added
10+
- **Mobile Accessibility:** Completed accessibility audit for all mobile screens.
11+
- **Features:**
12+
- Added `accessibilityLabel` to all interactive elements (buttons, inputs, list items).
13+
- Added `accessibilityRole` to ensure screen readers identify element types correctly.
14+
- Added `accessibilityHint` for clearer context on destructive actions or complex interactions.
15+
- Covered Auth, Dashboard, Groups, and Utility screens.
16+
- **Technical:** Updated all files in `mobile/screens/` to compliant with React Native accessibility standards.
17+
1018
- **Mobile Pull-to-Refresh:** Implemented native pull-to-refresh interactions with haptic feedback for key lists.
1119
- **Features:**
1220
- Integrated `RefreshControl` into `HomeScreen`, `FriendsScreen`, and `GroupDetailsScreen`.

.Jules/knowledge.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,17 @@ Commonly used components:
306306

307307
Most screens use `<View style={{ flex: 1 }}>` - consider wrapping in `SafeAreaView` for notched devices.
308308

309+
### Accessibility Patterns
310+
311+
**Date:** 2026-01-29
312+
**Context:** Auditing and fixing mobile accessibility
313+
314+
When building mobile screens with React Native Paper:
315+
1. **Explicit Labels:** Always add `accessibilityLabel` to `IconButton`, `FAB`, and `Card` components that act as buttons.
316+
2. **Roles:** Use `accessibilityRole="button"` for pressable elements, `accessibilityRole="header"` for titles.
317+
3. **Hints:** Use `accessibilityHint` for non-obvious actions (e.g., "Double tap to delete").
318+
4. **State:** For custom checkboxes or toggles, use `accessibilityState={{ checked: boolean }}`.
319+
309320
---
310321

311322
## API Response Patterns

.Jules/todo.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
- Size: ~40 lines
6565
- Added: 2026-01-01
6666

67-
- [ ] **[a11y]** Complete accessibility labels for all screens
67+
- [x] **[a11y]** Complete accessibility labels for all screens
68+
- Completed: 2026-01-29
6869
- Files: All screens in `mobile/screens/`
6970
- Context: Add accessibilityLabel, accessibilityHint, accessibilityRole throughout
7071
- Impact: Screen reader users can use app fully

mobile/screens/AccountScreen.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,30 +39,41 @@ const AccountScreen = ({ navigation }) => {
3939
title="Edit Profile"
4040
left={() => <List.Icon icon="account-edit" />}
4141
onPress={() => navigation.navigate("EditProfile")}
42+
accessibilityLabel="Edit Profile"
43+
accessibilityRole="button"
4244
/>
4345
<Divider />
4446
<List.Item
4547
title="Email Settings"
4648
left={() => <List.Icon icon="email-edit-outline" />}
4749
onPress={handleComingSoon}
50+
accessibilityLabel="Email Settings"
51+
accessibilityRole="button"
4852
/>
4953
<Divider />
5054
<List.Item
5155
title="Send Feedback"
5256
left={() => <List.Icon icon="message-alert-outline" />}
5357
onPress={handleComingSoon}
58+
accessibilityLabel="Send Feedback"
59+
accessibilityRole="button"
5460
/>
5561
<Divider />
5662
<List.Item
5763
title="Import from Splitwise"
5864
left={() => <List.Icon icon="import" />}
5965
onPress={() => navigation.navigate("SplitwiseImport")}
66+
accessibilityLabel="Import from Splitwise"
67+
accessibilityRole="button"
6068
/>
6169
<Divider />
6270
<List.Item
6371
title="Logout"
6472
left={() => <List.Icon icon="logout" />}
6573
onPress={handleLogout}
74+
accessibilityLabel="Logout"
75+
accessibilityRole="button"
76+
accessibilityHint="Logs you out of the application"
6677
/>
6778
</List.Section>
6879
</View>

mobile/screens/AddExpenseScreen.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ const AddExpenseScreen = ({ route, navigation }) => {
282282
label={member.user.name}
283283
status={selectedMembers[member.userId] ? "checked" : "unchecked"}
284284
onPress={() => handleMemberSelect(member.userId)}
285+
accessibilityLabel={`Select ${member.user.name}`}
286+
accessibilityRole="checkbox"
287+
accessibilityState={{
288+
checked: !!selectedMembers[member.userId],
289+
}}
285290
/>
286291
));
287292
case "exact":
@@ -295,6 +300,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
295300
}
296301
keyboardType="numeric"
297302
style={styles.splitInput}
303+
accessibilityLabel={`${member.user.name}'s exact amount`}
298304
/>
299305
));
300306
case "percentage":
@@ -308,6 +314,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
308314
}
309315
keyboardType="numeric"
310316
style={styles.splitInput}
317+
accessibilityLabel={`${member.user.name}'s percentage`}
311318
/>
312319
));
313320
case "shares":
@@ -321,6 +328,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
321328
}
322329
keyboardType="numeric"
323330
style={styles.splitInput}
331+
accessibilityLabel={`${member.user.name}'s shares`}
324332
/>
325333
));
326334
default:
@@ -351,20 +359,27 @@ const AddExpenseScreen = ({ route, navigation }) => {
351359
value={description}
352360
onChangeText={setDescription}
353361
style={styles.input}
362+
accessibilityLabel="Expense Description"
354363
/>
355364
<TextInput
356365
label="Amount"
357366
value={amount}
358367
onChangeText={setAmount}
359368
style={styles.input}
360369
keyboardType="numeric"
370+
accessibilityLabel="Expense Amount"
361371
/>
362372

363373
<Menu
364374
visible={menuVisible}
365375
onDismiss={() => setMenuVisible(false)}
366376
anchor={
367-
<Button onPress={() => setMenuVisible(true)}>
377+
<Button
378+
onPress={() => setMenuVisible(true)}
379+
accessibilityLabel={`Paid by ${selectedPayerName}`}
380+
accessibilityRole="button"
381+
accessibilityHint="Double tap to change payer"
382+
>
368383
Paid by: {selectedPayerName}
369384
</Button>
370385
}
@@ -442,6 +457,8 @@ const AddExpenseScreen = ({ route, navigation }) => {
442457
style={styles.button}
443458
loading={isSubmitting}
444459
disabled={isSubmitting}
460+
accessibilityLabel="Add Expense"
461+
accessibilityRole="button"
445462
>
446463
Add Expense
447464
</Button>

mobile/screens/EditProfileScreen.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ const EditProfileScreen = ({ navigation }) => {
103103
onPress={pickImage}
104104
icon="camera"
105105
style={styles.imageButton}
106+
accessibilityLabel="Change profile picture"
107+
accessibilityRole="button"
108+
accessibilityHint="Opens your media library to select a new photo"
106109
>
107110
{pickedImage ? "Change Photo" : "Add Photo"}
108111
</Button>
@@ -113,13 +116,16 @@ const EditProfileScreen = ({ navigation }) => {
113116
value={name}
114117
onChangeText={setName}
115118
style={styles.input}
119+
accessibilityLabel="Full Name"
116120
/>
117121
<Button
118122
mode="contained"
119123
onPress={handleUpdateProfile}
120124
loading={isSubmitting}
121125
disabled={isSubmitting}
122126
style={styles.button}
127+
accessibilityLabel="Save Changes"
128+
accessibilityRole="button"
123129
>
124130
Save Changes
125131
</Button>

mobile/screens/FriendsScreen.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ const FriendsScreen = () => {
100100
descriptionStyle={{
101101
color: item.netBalance !== 0 ? balanceColor : "gray",
102102
}}
103+
accessibilityRole="button"
104+
accessibilityLabel={`Friend ${item.name}. ${
105+
item.netBalance !== 0 ? balanceText : "Settled up"
106+
}`}
107+
accessibilityHint="Double tap to see balance breakdown"
103108
left={(props) =>
104109
imageUri ? (
105110
<Avatar.Image {...props} size={40} source={{ uri: imageUri }} />
@@ -207,7 +212,11 @@ const FriendsScreen = () => {
207212
<Appbar.Header>
208213
<Appbar.Content title="Friends" />
209214
</Appbar.Header>
210-
<View style={styles.skeletonContainer}>
215+
<View
216+
style={styles.skeletonContainer}
217+
accessibilityLabel="Loading friends list"
218+
accessibilityRole="progressbar"
219+
>
211220
{Array.from({ length: 5 }).map((_, i) => (
212221
<SkeletonRow key={i} />
213222
))}
@@ -234,6 +243,8 @@ const FriendsScreen = () => {
234243
size={16}
235244
onPress={() => setShowTooltip(false)}
236245
style={styles.closeButton}
246+
accessibilityLabel="Close tooltip"
247+
accessibilityRole="button"
237248
/>
238249
</View>
239250
</View>

mobile/screens/GroupDetailsScreen.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ const GroupDetailsScreen = ({ route, navigation }) => {
6868
<IconButton
6969
icon="cog"
7070
onPress={() => navigation.navigate("GroupSettings", { groupId })}
71+
accessibilityLabel="Group settings"
72+
accessibilityRole="button"
7173
/>
7274
),
7375
});
@@ -101,7 +103,13 @@ const GroupDetailsScreen = ({ route, navigation }) => {
101103
}
102104

103105
return (
104-
<Card style={styles.card}>
106+
<Card
107+
style={styles.card}
108+
accessibilityRole="button"
109+
accessibilityLabel={`Expense: ${item.description}, Amount: ${formatCurrency(
110+
item.amount
111+
)}. Paid by ${getMemberName(item.paidBy || item.createdBy)}. ${balanceText}`}
112+
>
105113
<Card.Content>
106114
<Title>{item.description}</Title>
107115
<Paragraph>Amount: {formatCurrency(item.amount)}</Paragraph>
@@ -227,6 +235,8 @@ const GroupDetailsScreen = ({ route, navigation }) => {
227235
style={styles.fab}
228236
icon="plus"
229237
onPress={() => navigation.navigate("AddExpense", { groupId: groupId })}
238+
accessibilityLabel="Add expense"
239+
accessibilityRole="button"
230240
/>
231241
</View>
232242
);

mobile/screens/GroupSettingsScreen.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ const GroupSettingsScreen = ({ route, navigation }) => {
280280
<IconButton
281281
icon="account-remove"
282282
onPress={() => onKick(m.userId, displayName)}
283+
accessibilityLabel={`Remove ${displayName} from group`}
284+
accessibilityRole="button"
285+
accessibilityHint="Removes this member from the group"
283286
/>
284287
) : null
285288
}
@@ -307,6 +310,7 @@ const GroupSettingsScreen = ({ route, navigation }) => {
307310
onChangeText={setName}
308311
editable={!!isAdmin}
309312
style={{ marginBottom: 12 }}
313+
accessibilityLabel="Group Name"
310314
/>
311315
<Text style={{ marginBottom: 8 }}>Icon</Text>
312316
<View style={styles.iconRow}>
@@ -317,6 +321,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
317321
style={styles.iconBtn}
318322
onPress={() => setIcon(i)}
319323
disabled={!isAdmin}
324+
accessibilityLabel={`Select icon ${i}`}
325+
accessibilityRole="button"
320326
>
321327
{i}
322328
</Button>
@@ -329,6 +335,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
329335
disabled={!isAdmin}
330336
icon="image"
331337
style={{ marginRight: 12 }}
338+
accessibilityLabel="Change group image"
339+
accessibilityRole="button"
332340
>
333341
{pickedImage ? "Change Image" : "Upload Image"}
334342
</Button>
@@ -354,6 +362,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
354362
loading={saving}
355363
disabled={saving}
356364
onPress={onSave}
365+
accessibilityLabel="Save Changes"
366+
accessibilityRole="button"
357367
>
358368
Save Changes
359369
</Button>
@@ -376,6 +386,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
376386
mode="outlined"
377387
onPress={onShareInvite}
378388
icon="share-variant"
389+
accessibilityLabel="Share invite code"
390+
accessibilityRole="button"
379391
>
380392
Share invite
381393
</Button>
@@ -392,6 +404,9 @@ const GroupSettingsScreen = ({ route, navigation }) => {
392404
textColor="#d32f2f"
393405
onPress={onLeave}
394406
icon="logout-variant"
407+
accessibilityLabel="Leave Group"
408+
accessibilityRole="button"
409+
accessibilityHint="You must settle balances before leaving"
395410
>
396411
Leave Group
397412
</Button>
@@ -402,6 +417,9 @@ const GroupSettingsScreen = ({ route, navigation }) => {
402417
onPress={onDeleteGroup}
403418
icon="delete"
404419
style={{ marginTop: 8 }}
420+
accessibilityLabel="Delete Group"
421+
accessibilityRole="button"
422+
accessibilityHint="Permanently deletes the group and all data"
405423
>
406424
Delete Group
407425
</Button>

mobile/screens/HomeScreen.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ const HomeScreen = ({ navigation }) => {
185185
groupIcon,
186186
})
187187
}
188+
accessibilityRole="button"
189+
accessibilityLabel={`Group ${item.name}. ${getSettlementStatusText()}`}
190+
accessibilityHint="Double tap to view group details"
188191
>
189192
<Card.Title
190193
title={item.name}
@@ -219,12 +222,15 @@ const HomeScreen = ({ navigation }) => {
219222
value={newGroupName}
220223
onChangeText={setNewGroupName}
221224
style={styles.input}
225+
accessibilityLabel="New group name"
222226
/>
223227
<Button
224228
mode="contained"
225229
onPress={handleCreateGroup}
226230
loading={isCreatingGroup}
227231
disabled={isCreatingGroup}
232+
accessibilityLabel="Create Group"
233+
accessibilityRole="button"
228234
>
229235
Create
230236
</Button>
@@ -233,12 +239,19 @@ const HomeScreen = ({ navigation }) => {
233239

234240
<Appbar.Header>
235241
<Appbar.Content title="Your Groups" />
236-
<Appbar.Action icon="plus" onPress={showModal} />
242+
<Appbar.Action
243+
icon="plus"
244+
onPress={showModal}
245+
accessibilityLabel="Create new group"
246+
accessibilityRole="button"
247+
/>
237248
<Appbar.Action
238249
icon="account-plus"
239250
onPress={() =>
240251
navigation.navigate("JoinGroup", { onGroupJoined: fetchGroups })
241252
}
253+
accessibilityLabel="Join a group"
254+
accessibilityRole="button"
242255
/>
243256
</Appbar.Header>
244257

0 commit comments

Comments
 (0)