diff --git a/.Jules/changelog.md b/.Jules/changelog.md index b3cfda1..007d653 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,14 @@ ## [Unreleased] ### Added +- **Mobile Accessibility:** Completed accessibility audit for all mobile screens. + - **Features:** + - Added `accessibilityLabel` to all interactive elements (buttons, inputs, list items). + - Added `accessibilityRole` to ensure screen readers identify element types correctly. + - Added `accessibilityHint` for clearer context on destructive actions or complex interactions. + - Covered Auth, Dashboard, Groups, and Utility screens. + - **Technical:** Updated all files in `mobile/screens/` to compliant with React Native accessibility standards. + - **Mobile Pull-to-Refresh:** Implemented native pull-to-refresh interactions with haptic feedback for key lists. - **Features:** - Integrated `RefreshControl` into `HomeScreen`, `FriendsScreen`, and `GroupDetailsScreen`. diff --git a/.Jules/knowledge.md b/.Jules/knowledge.md index 3361c5d..43a9ab0 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -306,6 +306,17 @@ Commonly used components: Most screens use `` - consider wrapping in `SafeAreaView` for notched devices. +### Accessibility Patterns + +**Date:** 2026-01-29 +**Context:** Auditing and fixing mobile accessibility + +When building mobile screens with React Native Paper: +1. **Explicit Labels:** Always add `accessibilityLabel` to `IconButton`, `FAB`, and `Card` components that act as buttons. +2. **Roles:** Use `accessibilityRole="button"` for pressable elements, `accessibilityRole="header"` for titles. +3. **Hints:** Use `accessibilityHint` for non-obvious actions (e.g., "Double tap to delete"). +4. **State:** For custom checkboxes or toggles, use `accessibilityState={{ checked: boolean }}`. + --- ## API Response Patterns diff --git a/.Jules/todo.md b/.Jules/todo.md index 7797cfb..de49cb8 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -64,7 +64,8 @@ - Size: ~40 lines - Added: 2026-01-01 -- [ ] **[a11y]** Complete accessibility labels for all screens +- [x] **[a11y]** Complete accessibility labels for all screens + - Completed: 2026-01-29 - Files: All screens in `mobile/screens/` - Context: Add accessibilityLabel, accessibilityHint, accessibilityRole throughout - Impact: Screen reader users can use app fully diff --git a/mobile/screens/AccountScreen.js b/mobile/screens/AccountScreen.js index 5c73504..c4ed869 100644 --- a/mobile/screens/AccountScreen.js +++ b/mobile/screens/AccountScreen.js @@ -39,30 +39,41 @@ const AccountScreen = ({ navigation }) => { title="Edit Profile" left={() => } onPress={() => navigation.navigate("EditProfile")} + accessibilityLabel="Edit Profile" + accessibilityRole="button" /> } onPress={handleComingSoon} + accessibilityLabel="Email Settings" + accessibilityRole="button" /> } onPress={handleComingSoon} + accessibilityLabel="Send Feedback" + accessibilityRole="button" /> } onPress={() => navigation.navigate("SplitwiseImport")} + accessibilityLabel="Import from Splitwise" + accessibilityRole="button" /> } onPress={handleLogout} + accessibilityLabel="Logout" + accessibilityRole="button" + accessibilityHint="Logs you out of the application" /> diff --git a/mobile/screens/AddExpenseScreen.js b/mobile/screens/AddExpenseScreen.js index 59cb65e..f5f58a3 100644 --- a/mobile/screens/AddExpenseScreen.js +++ b/mobile/screens/AddExpenseScreen.js @@ -282,6 +282,11 @@ const AddExpenseScreen = ({ route, navigation }) => { label={member.user.name} status={selectedMembers[member.userId] ? "checked" : "unchecked"} onPress={() => handleMemberSelect(member.userId)} + accessibilityLabel={`Select ${member.user.name}`} + accessibilityRole="checkbox" + accessibilityState={{ + checked: !!selectedMembers[member.userId], + }} /> )); case "exact": @@ -295,6 +300,7 @@ const AddExpenseScreen = ({ route, navigation }) => { } keyboardType="numeric" style={styles.splitInput} + accessibilityLabel={`${member.user.name}'s exact amount`} /> )); case "percentage": @@ -308,6 +314,7 @@ const AddExpenseScreen = ({ route, navigation }) => { } keyboardType="numeric" style={styles.splitInput} + accessibilityLabel={`${member.user.name}'s percentage`} /> )); case "shares": @@ -321,6 +328,7 @@ const AddExpenseScreen = ({ route, navigation }) => { } keyboardType="numeric" style={styles.splitInput} + accessibilityLabel={`${member.user.name}'s shares`} /> )); default: @@ -351,6 +359,7 @@ const AddExpenseScreen = ({ route, navigation }) => { value={description} onChangeText={setDescription} style={styles.input} + accessibilityLabel="Expense Description" /> { onChangeText={setAmount} style={styles.input} keyboardType="numeric" + accessibilityLabel="Expense Amount" /> setMenuVisible(false)} anchor={ - } @@ -442,6 +457,8 @@ const AddExpenseScreen = ({ route, navigation }) => { style={styles.button} loading={isSubmitting} disabled={isSubmitting} + accessibilityLabel="Add Expense" + accessibilityRole="button" > Add Expense diff --git a/mobile/screens/EditProfileScreen.js b/mobile/screens/EditProfileScreen.js index 8201b70..33a8faf 100644 --- a/mobile/screens/EditProfileScreen.js +++ b/mobile/screens/EditProfileScreen.js @@ -103,6 +103,9 @@ const EditProfileScreen = ({ navigation }) => { onPress={pickImage} icon="camera" style={styles.imageButton} + accessibilityLabel="Change profile picture" + accessibilityRole="button" + accessibilityHint="Opens your media library to select a new photo" > {pickedImage ? "Change Photo" : "Add Photo"} @@ -113,6 +116,7 @@ const EditProfileScreen = ({ navigation }) => { value={name} onChangeText={setName} style={styles.input} + accessibilityLabel="Full Name" /> diff --git a/mobile/screens/FriendsScreen.js b/mobile/screens/FriendsScreen.js index b145efd..ad9bea2 100644 --- a/mobile/screens/FriendsScreen.js +++ b/mobile/screens/FriendsScreen.js @@ -100,6 +100,11 @@ const FriendsScreen = () => { descriptionStyle={{ color: item.netBalance !== 0 ? balanceColor : "gray", }} + accessibilityRole="button" + accessibilityLabel={`Friend ${item.name}. ${ + item.netBalance !== 0 ? balanceText : "Settled up" + }`} + accessibilityHint="Double tap to see balance breakdown" left={(props) => imageUri ? ( @@ -207,7 +212,11 @@ const FriendsScreen = () => { - + {Array.from({ length: 5 }).map((_, i) => ( ))} @@ -234,6 +243,8 @@ const FriendsScreen = () => { size={16} onPress={() => setShowTooltip(false)} style={styles.closeButton} + accessibilityLabel="Close tooltip" + accessibilityRole="button" /> diff --git a/mobile/screens/GroupDetailsScreen.js b/mobile/screens/GroupDetailsScreen.js index 8913a35..bcb0018 100644 --- a/mobile/screens/GroupDetailsScreen.js +++ b/mobile/screens/GroupDetailsScreen.js @@ -68,6 +68,8 @@ const GroupDetailsScreen = ({ route, navigation }) => { navigation.navigate("GroupSettings", { groupId })} + accessibilityLabel="Group settings" + accessibilityRole="button" /> ), }); @@ -101,7 +103,13 @@ const GroupDetailsScreen = ({ route, navigation }) => { } return ( - + {item.description} Amount: {formatCurrency(item.amount)} @@ -227,6 +235,8 @@ const GroupDetailsScreen = ({ route, navigation }) => { style={styles.fab} icon="plus" onPress={() => navigation.navigate("AddExpense", { groupId: groupId })} + accessibilityLabel="Add expense" + accessibilityRole="button" /> ); diff --git a/mobile/screens/GroupSettingsScreen.js b/mobile/screens/GroupSettingsScreen.js index 90de5d1..f45c099 100644 --- a/mobile/screens/GroupSettingsScreen.js +++ b/mobile/screens/GroupSettingsScreen.js @@ -280,6 +280,9 @@ const GroupSettingsScreen = ({ route, navigation }) => { onKick(m.userId, displayName)} + accessibilityLabel={`Remove ${displayName} from group`} + accessibilityRole="button" + accessibilityHint="Removes this member from the group" /> ) : null } @@ -307,6 +310,7 @@ const GroupSettingsScreen = ({ route, navigation }) => { onChangeText={setName} editable={!!isAdmin} style={{ marginBottom: 12 }} + accessibilityLabel="Group Name" /> Icon @@ -317,6 +321,8 @@ const GroupSettingsScreen = ({ route, navigation }) => { style={styles.iconBtn} onPress={() => setIcon(i)} disabled={!isAdmin} + accessibilityLabel={`Select icon ${i}`} + accessibilityRole="button" > {i} @@ -329,6 +335,8 @@ const GroupSettingsScreen = ({ route, navigation }) => { disabled={!isAdmin} icon="image" style={{ marginRight: 12 }} + accessibilityLabel="Change group image" + accessibilityRole="button" > {pickedImage ? "Change Image" : "Upload Image"} @@ -354,6 +362,8 @@ const GroupSettingsScreen = ({ route, navigation }) => { loading={saving} disabled={saving} onPress={onSave} + accessibilityLabel="Save Changes" + accessibilityRole="button" > Save Changes @@ -376,6 +386,8 @@ const GroupSettingsScreen = ({ route, navigation }) => { mode="outlined" onPress={onShareInvite} icon="share-variant" + accessibilityLabel="Share invite code" + accessibilityRole="button" > Share invite @@ -392,6 +404,9 @@ const GroupSettingsScreen = ({ route, navigation }) => { textColor="#d32f2f" onPress={onLeave} icon="logout-variant" + accessibilityLabel="Leave Group" + accessibilityRole="button" + accessibilityHint="You must settle balances before leaving" > Leave Group @@ -402,6 +417,9 @@ const GroupSettingsScreen = ({ route, navigation }) => { onPress={onDeleteGroup} icon="delete" style={{ marginTop: 8 }} + accessibilityLabel="Delete Group" + accessibilityRole="button" + accessibilityHint="Permanently deletes the group and all data" > Delete Group diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js index ada77b5..373bb0a 100644 --- a/mobile/screens/HomeScreen.js +++ b/mobile/screens/HomeScreen.js @@ -185,6 +185,9 @@ const HomeScreen = ({ navigation }) => { groupIcon, }) } + accessibilityRole="button" + accessibilityLabel={`Group ${item.name}. ${getSettlementStatusText()}`} + accessibilityHint="Double tap to view group details" > { value={newGroupName} onChangeText={setNewGroupName} style={styles.input} + accessibilityLabel="New group name" /> @@ -233,12 +239,19 @@ const HomeScreen = ({ navigation }) => { - + navigation.navigate("JoinGroup", { onGroupJoined: fetchGroups }) } + accessibilityLabel="Join a group" + accessibilityRole="button" /> diff --git a/mobile/screens/JoinGroupScreen.js b/mobile/screens/JoinGroupScreen.js index 153e4ea..a1fc05b 100644 --- a/mobile/screens/JoinGroupScreen.js +++ b/mobile/screens/JoinGroupScreen.js @@ -46,6 +46,7 @@ const JoinGroupScreen = ({ navigation, route }) => { onChangeText={setJoinCode} style={styles.input} autoCapitalize="characters" + accessibilityLabel="Group Join Code" /> diff --git a/mobile/screens/LoginScreen.js b/mobile/screens/LoginScreen.js index 076e995..aa5e94a 100644 --- a/mobile/screens/LoginScreen.js +++ b/mobile/screens/LoginScreen.js @@ -32,6 +32,7 @@ const LoginScreen = ({ navigation }) => { style={styles.input} keyboardType="email-address" autoCapitalize="none" + accessibilityLabel="Email address" /> { onChangeText={setPassword} style={styles.input} secureTextEntry + accessibilityLabel="Password" /> - diff --git a/mobile/screens/SignupScreen.js b/mobile/screens/SignupScreen.js index 5594be6..c3ba18f 100644 --- a/mobile/screens/SignupScreen.js +++ b/mobile/screens/SignupScreen.js @@ -43,6 +43,7 @@ const SignupScreen = ({ navigation }) => { onChangeText={setName} style={styles.input} autoCapitalize="words" + accessibilityLabel="Full Name" /> { style={styles.input} keyboardType="email-address" autoCapitalize="none" + accessibilityLabel="Email address" /> { onChangeText={setPassword} style={styles.input} secureTextEntry + accessibilityLabel="Password" /> { onChangeText={setConfirmPassword} style={styles.input} secureTextEntry + accessibilityLabel="Confirm Password" /> - diff --git a/mobile/screens/SplitwiseImportScreen.js b/mobile/screens/SplitwiseImportScreen.js index 6d1b06f..8abf5cd 100644 --- a/mobile/screens/SplitwiseImportScreen.js +++ b/mobile/screens/SplitwiseImportScreen.js @@ -66,6 +66,9 @@ const SplitwiseImportScreen = ({ navigation }) => { style={styles.button} icon={loading ? undefined : "login"} loading={loading} + accessibilityLabel="Connect with Splitwise" + accessibilityRole="button" + accessibilityHint="Opens Splitwise in your browser to authorize access" > {loading ? "Connecting..." : "Connect with Splitwise"}