about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/auth/SplashScreen.tsx10
-rw-r--r--src/view/com/auth/SplashScreen.web.tsx13
-rw-r--r--src/view/com/auth/create/CreateAccount.tsx20
-rw-r--r--src/view/com/auth/create/Step1.tsx14
-rw-r--r--src/view/com/auth/create/Step2.tsx33
-rw-r--r--src/view/com/auth/create/Step3.tsx3
-rw-r--r--src/view/com/auth/login/Login.tsx100
-rw-r--r--src/view/com/auth/util/TextInput.tsx25
-rw-r--r--src/view/com/composer/Composer.tsx274
-rw-r--r--src/view/com/composer/ExternalEmbed.tsx8
-rw-r--r--src/view/com/composer/Prompt.tsx5
-rw-r--r--src/view/com/composer/photos/Gallery.tsx11
-rw-r--r--src/view/com/composer/photos/OpenCameraBtn.tsx16
-rw-r--r--src/view/com/composer/photos/SelectPhotoBtn.tsx16
-rw-r--r--src/view/com/composer/text-input/TextInput.tsx41
-rw-r--r--src/view/com/composer/text-input/TextInput.web.tsx4
-rw-r--r--src/view/com/composer/text-input/mobile/Autocomplete.tsx4
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx6
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx3
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx7
-rw-r--r--src/view/com/lightbox/Lightbox.web.tsx23
-rw-r--r--src/view/com/modals/AddAppPasswords.tsx8
-rw-r--r--src/view/com/modals/AltImage.tsx19
-rw-r--r--src/view/com/modals/AltImageRead.tsx7
-rw-r--r--src/view/com/modals/ChangeHandle.tsx37
-rw-r--r--src/view/com/modals/Confirm.tsx7
-rw-r--r--src/view/com/modals/ContentFilteringSettings.tsx47
-rw-r--r--src/view/com/modals/DeleteAccount.tsx36
-rw-r--r--src/view/com/modals/EditProfile.tsx17
-rw-r--r--src/view/com/modals/InviteCodes.tsx10
-rw-r--r--src/view/com/modals/Modal.web.tsx3
-rw-r--r--src/view/com/modals/ReportAccount.tsx5
-rw-r--r--src/view/com/modals/ReportPost.tsx5
-rw-r--r--src/view/com/modals/Repost.tsx19
-rw-r--r--src/view/com/modals/ServerInput.tsx25
-rw-r--r--src/view/com/modals/Waitlist.tsx16
-rw-r--r--src/view/com/modals/crop-image/CropImage.web.tsx35
-rw-r--r--src/view/com/notifications/FeedItem.tsx83
-rw-r--r--src/view/com/pager/FeedsTabBarMobile.tsx5
-rw-r--r--src/view/com/post-thread/PostThread.tsx12
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx13
-rw-r--r--src/view/com/profile/ProfileHeader.tsx48
-rw-r--r--src/view/com/search/HeaderWithInput.tsx21
-rw-r--r--src/view/com/util/BottomSheetCustomBackdrop.tsx14
-rw-r--r--src/view/com/util/Link.tsx34
-rw-r--r--src/view/com/util/Picker.tsx157
-rw-r--r--src/view/com/util/PostCtrls.tsx159
-rw-r--r--src/view/com/util/Selector.tsx6
-rw-r--r--src/view/com/util/UserAvatar.tsx7
-rw-r--r--src/view/com/util/UserBanner.tsx8
-rw-r--r--src/view/com/util/ViewHeader.tsx13
-rw-r--r--src/view/com/util/ViewSelector.tsx7
-rw-r--r--src/view/com/util/error/ErrorMessage.tsx5
-rw-r--r--src/view/com/util/error/ErrorScreen.tsx4
-rw-r--r--src/view/com/util/fab/FABInner.tsx18
-rw-r--r--src/view/com/util/forms/Button.tsx4
-rw-r--r--src/view/com/util/forms/DropdownButton.tsx62
-rw-r--r--src/view/com/util/images/AutoSizedImage.tsx9
-rw-r--r--src/view/com/util/images/Gallery.tsx13
-rw-r--r--src/view/com/util/images/Image.tsx4
-rw-r--r--src/view/com/util/images/ImageHorzList.tsx22
-rw-r--r--src/view/com/util/load-latest/LoadLatestBtn.web.tsx5
-rw-r--r--src/view/com/util/load-latest/LoadLatestBtnMobile.tsx5
-rw-r--r--src/view/com/util/moderation/ContentHider.tsx9
-rw-r--r--src/view/com/util/moderation/PostHider.tsx3
-rw-r--r--src/view/com/util/post-embeds/index.tsx5
66 files changed, 1044 insertions, 643 deletions
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
index f98bed120..41787bb5f 100644
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -28,7 +28,10 @@ export const SplashScreen = ({
             <TouchableOpacity
               testID="createAccountButton"
               style={[styles.btn, {backgroundColor: colors.blue3}]}
-              onPress={onPressCreateAccount}>
+              onPress={onPressCreateAccount}
+              accessibilityRole="button"
+              accessibilityLabel="Create new account"
+              accessibilityHint="Opens flow to create a new Bluesky account">
               <Text style={[s.white, styles.btnLabel]}>
                 Create a new account
               </Text>
@@ -36,7 +39,10 @@ export const SplashScreen = ({
             <TouchableOpacity
               testID="signInButton"
               style={[styles.btn, pal.btn]}
-              onPress={onPressSignin}>
+              onPress={onPressSignin}
+              accessibilityRole="button"
+              accessibilityLabel="Sign in"
+              accessibilityHint="Opens flow to sign into your existing Bluesky account">
               <Text style={[pal.text, styles.btnLabel]}>Sign in</Text>
             </TouchableOpacity>
           </View>
diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
index 7fac5a8c0..9236968c4 100644
--- a/src/view/com/auth/SplashScreen.web.tsx
+++ b/src/view/com/auth/SplashScreen.web.tsx
@@ -43,7 +43,9 @@ export const SplashScreen = ({
             <TouchableOpacity
               testID="createAccountButton"
               style={[styles.btn, {backgroundColor: colors.blue3}]}
-              onPress={onPressCreateAccount}>
+              onPress={onPressCreateAccount}
+              // TODO: web accessibility
+              accessibilityRole="button">
               <Text style={[s.white, styles.btnLabel]}>
                 Create a new account
               </Text>
@@ -51,7 +53,9 @@ export const SplashScreen = ({
             <TouchableOpacity
               testID="signInButton"
               style={[styles.btn, pal.btn]}
-              onPress={onPressSignin}>
+              onPress={onPressSignin}
+              // TODO: web accessibility
+              accessibilityRole="button">
               <Text style={[pal.text, styles.btnLabel]}>Sign in</Text>
             </TouchableOpacity>
           </View>
@@ -60,7 +64,10 @@ export const SplashScreen = ({
             style={[styles.notice, pal.textLight]}
             lineHeight={1.3}>
             Bluesky will launch soon.{' '}
-            <TouchableOpacity onPress={onPressWaitlist}>
+            <TouchableOpacity
+              onPress={onPressWaitlist}
+              // TODO: web accessibility
+              accessibilityRole="button">
               <Text type="xl" style={pal.link}>
                 Join the waitlist
               </Text>
diff --git a/src/view/com/auth/create/CreateAccount.tsx b/src/view/com/auth/create/CreateAccount.tsx
index 467b87948..ac03081df 100644
--- a/src/view/com/auth/create/CreateAccount.tsx
+++ b/src/view/com/auth/create/CreateAccount.tsx
@@ -72,14 +72,24 @@ export const CreateAccount = observer(
             {model.step === 3 && <Step3 model={model} />}
           </View>
           <View style={[s.flexRow, s.pl20, s.pr20]}>
-            <TouchableOpacity onPress={onPressBackInner} testID="backBtn">
+            <TouchableOpacity
+              onPress={onPressBackInner}
+              testID="backBtn"
+              accessibilityRole="button"
+              accessibilityLabel="Go back"
+              accessibilityHint="Navigates to the previous screen">
               <Text type="xl" style={pal.link}>
                 Back
               </Text>
             </TouchableOpacity>
             <View style={s.flex1} />
             {model.canNext ? (
-              <TouchableOpacity testID="nextBtn" onPress={onPressNext}>
+              <TouchableOpacity
+                testID="nextBtn"
+                onPress={onPressNext}
+                accessibilityRole="button"
+                accessibilityLabel="Go to next"
+                accessibilityHint="Navigates to the next screen">
                 {model.isProcessing ? (
                   <ActivityIndicator />
                 ) : (
@@ -91,7 +101,11 @@ export const CreateAccount = observer(
             ) : model.didServiceDescriptionFetchFail ? (
               <TouchableOpacity
                 testID="retryConnectBtn"
-                onPress={onPressRetryConnect}>
+                onPress={onPressRetryConnect}
+                accessibilityRole="button"
+                accessibilityLabel="Retry"
+                accessibilityHint="Retries account creation"
+                accessibilityLiveRegion="polite">
                 <Text type="xl-bold" style={[pal.link, s.pr5]}>
                   Retry
                 </Text>
diff --git a/src/view/com/auth/create/Step1.tsx b/src/view/com/auth/create/Step1.tsx
index ca964ede2..ac0d706d7 100644
--- a/src/view/com/auth/create/Step1.tsx
+++ b/src/view/com/auth/create/Step1.tsx
@@ -57,7 +57,7 @@ export const Step1 = observer(({model}: {model: CreateAccountModel}) => {
     <View>
       <StepHeader step="1" title="Your hosting provider" />
       <Text style={[pal.text, s.mb10]}>
-        This is the company that keeps you online.
+        This is the service that keeps you online.
       </Text>
       <Option
         testID="blueskyServerBtn"
@@ -72,7 +72,7 @@ export const Step1 = observer(({model}: {model: CreateAccountModel}) => {
         label="Other"
         onPress={onPressOther}>
         <View style={styles.otherForm}>
-          <Text style={[pal.text, s.mb5]}>
+          <Text nativeID="addressProvider" style={[pal.text, s.mb5]}>
             Enter the address of your provider:
           </Text>
           <TextInput
@@ -82,6 +82,9 @@ export const Step1 = observer(({model}: {model: CreateAccountModel}) => {
             value={model.serviceUrl}
             editable
             onChange={onChangeServiceUrl}
+            accessibilityHint="Input hosting provider address"
+            accessibilityLabel="Hosting provider address"
+            accessibilityLabelledBy="addressProvider"
           />
           {LOGIN_INCLUDE_DEV_SERVERS && (
             <View style={[s.flexRow, s.mt10]}>
@@ -136,7 +139,12 @@ function Option({
 
   return (
     <View style={[styles.option, pal.border]}>
-      <TouchableWithoutFeedback onPress={onPress} testID={testID}>
+      <TouchableWithoutFeedback
+        onPress={onPress}
+        testID={testID}
+        accessibilityRole="button"
+        accessibilityLabel={label}
+        accessibilityHint={`Sets hosting provider to ${label}`}>
         <View style={styles.optionHeading}>
           <View style={[styles.circle, pal.border]}>
             {isSelected ? (
diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx
index 375f80796..eceee50d3 100644
--- a/src/view/com/auth/create/Step2.tsx
+++ b/src/view/com/auth/create/Step2.tsx
@@ -41,6 +41,9 @@ export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
             value={model.inviteCode}
             editable
             onChange={model.setInviteCode}
+            accessibilityRole="button"
+            accessibilityLabel="Invite code"
+            accessibilityHint="Input invite code to proceed"
           />
         </View>
       )}
@@ -48,7 +51,11 @@ export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
       {!model.inviteCode && model.isInviteCodeRequired ? (
         <Text style={[s.alignBaseline, pal.text]}>
           Don't have an invite code?{' '}
-          <TouchableWithoutFeedback onPress={onPressWaitlist}>
+          <TouchableWithoutFeedback
+            onPress={onPressWaitlist}
+            accessibilityRole="button"
+            accessibilityLabel="Waitlist"
+            accessibilityHint="Opens Bluesky waitlist form">
             <Text style={pal.link}>Join the waitlist</Text>
           </TouchableWithoutFeedback>{' '}
           to try the beta before it's publicly available.
@@ -56,7 +63,7 @@ export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
       ) : (
         <>
           <View style={s.pb20}>
-            <Text type="md-medium" style={[pal.text, s.mb2]}>
+            <Text type="md-medium" style={[pal.text, s.mb2]} nativeID="email">
               Email address
             </Text>
             <TextInput
@@ -66,11 +73,17 @@ export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
               value={model.email}
               editable
               onChange={model.setEmail}
+              accessibilityLabel="Email"
+              accessibilityHint="Input email for Bluesky waitlist"
+              accessibilityLabelledBy="email"
             />
           </View>
 
           <View style={s.pb20}>
-            <Text type="md-medium" style={[pal.text, s.mb2]}>
+            <Text
+              type="md-medium"
+              style={[pal.text, s.mb2]}
+              nativeID="password">
               Password
             </Text>
             <TextInput
@@ -81,17 +94,27 @@ export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
               editable
               secureTextEntry
               onChange={model.setPassword}
+              accessibilityLabel="Password"
+              accessibilityHint="Set password"
+              accessibilityLabelledBy="password"
             />
           </View>
 
           <View style={s.pb20}>
-            <Text type="md-medium" style={[pal.text, s.mb2]}>
+            <Text
+              type="md-medium"
+              style={[pal.text, s.mb2]}
+              nativeID="legalCheck">
               Legal check
             </Text>
             <TouchableOpacity
               testID="is13Input"
               style={[styles.toggleBtn, pal.border]}
-              onPress={() => model.setIs13(!model.is13)}>
+              onPress={() => model.setIs13(!model.is13)}
+              accessibilityRole="checkbox"
+              accessibilityLabel="Verify age"
+              accessibilityHint="Verifies that I am at least 13 years of age"
+              accessibilityLabelledBy="legalCheck">
               <View style={[pal.borderDark, styles.checkbox]}>
                 {model.is13 && (
                   <FontAwesomeIcon icon="check" style={s.blue3} size={16} />
diff --git a/src/view/com/auth/create/Step3.tsx b/src/view/com/auth/create/Step3.tsx
index 13ab39a10..3d9d47628 100644
--- a/src/view/com/auth/create/Step3.tsx
+++ b/src/view/com/auth/create/Step3.tsx
@@ -23,6 +23,9 @@ export const Step3 = observer(({model}: {model: CreateAccountModel}) => {
           value={model.handle}
           editable
           onChange={model.setHandle}
+          // TODO: Add explicit text label
+          accessibilityLabel="User handle"
+          accessibilityHint="Input your user handle"
         />
         <Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
           Your full handle will be{' '}
diff --git a/src/view/com/auth/login/Login.tsx b/src/view/com/auth/login/Login.tsx
index eff1642f0..37558fb54 100644
--- a/src/view/com/auth/login/Login.tsx
+++ b/src/view/com/auth/login/Login.tsx
@@ -195,7 +195,10 @@ const ChooseAccountForm = ({
           testID={`chooseAccountBtn-${account.handle}`}
           key={account.did}
           style={[pal.view, pal.border, styles.account]}
-          onPress={() => onTryAccount(account)}>
+          onPress={() => onTryAccount(account)}
+          accessibilityRole="button"
+          accessibilityLabel={`Sign in as ${account.handle}`}
+          accessibilityHint="Double tap to sign in">
           <View
             style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}>
             <View style={s.p10}>
@@ -220,7 +223,10 @@ const ChooseAccountForm = ({
       <TouchableOpacity
         testID="chooseNewAccountBtn"
         style={[pal.view, pal.border, styles.account, styles.accountLast]}
-        onPress={() => onSelectAccount(undefined)}>
+        onPress={() => onSelectAccount(undefined)}
+        accessibilityRole="button"
+        accessibilityLabel="Login to account that is not listed"
+        accessibilityHint="">
         <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}>
           <Text style={[styles.accountText, styles.accountTextOther]}>
             <Text type="lg" style={pal.text}>
@@ -235,7 +241,11 @@ const ChooseAccountForm = ({
         </View>
       </TouchableOpacity>
       <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}>
-        <TouchableOpacity onPress={onPressBack}>
+        <TouchableOpacity
+          onPress={onPressBack}
+          accessibilityRole="button"
+          accessibilityLabel="Go back"
+          accessibilityHint="Navigates to the previous screen">
           <Text type="xl" style={[pal.link, s.pl5]}>
             Back
           </Text>
@@ -351,7 +361,10 @@ const LoginForm = ({
           <TouchableOpacity
             testID="loginSelectServiceButton"
             style={styles.textBtn}
-            onPress={onPressSelectService}>
+            onPress={onPressSelectService}
+            accessibilityRole="button"
+            accessibilityLabel="Select service"
+            accessibilityHint="Sets server for the Bluesky client">
             <Text type="xl" style={[pal.text, styles.textBtnLabel]}>
               {toNiceDomain(serviceUrl)}
             </Text>
@@ -386,6 +399,8 @@ const LoginForm = ({
             value={identifier}
             onChangeText={str => setIdentifier((str || '').toLowerCase())}
             editable={!isProcessing}
+            accessibilityLabel="Username or email address"
+            accessibilityHint="Input the username or email address you used at signup"
           />
         </View>
         <View style={[pal.borderDark, styles.groupContent]}>
@@ -402,14 +417,28 @@ const LoginForm = ({
             autoCorrect={false}
             keyboardAppearance={theme.colorScheme}
             secureTextEntry
+            // HACK
+            // mitigates a known issue where the secure password prompt interferes
+            // https://github.com/facebook/react-native/issues/21911
+            // prf
+            textContentType="oneTimeCode"
             value={password}
             onChangeText={setPassword}
             editable={!isProcessing}
+            accessibilityLabel="Password"
+            accessibilityHint={
+              identifier === ''
+                ? 'Input your password'
+                : `Input the password tied to ${identifier}`
+            }
           />
           <TouchableOpacity
             testID="forgotPasswordButton"
             style={styles.textInputInnerBtn}
-            onPress={onPressForgotPassword}>
+            onPress={onPressForgotPassword}
+            accessibilityRole="button"
+            accessibilityLabel="Forgot password"
+            accessibilityHint="Opens password reset form">
             <Text style={pal.link}>Forgot</Text>
           </TouchableOpacity>
         </View>
@@ -425,7 +454,11 @@ const LoginForm = ({
         </View>
       ) : undefined}
       <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}>
-        <TouchableOpacity onPress={onPressBack}>
+        <TouchableOpacity
+          onPress={onPressBack}
+          accessibilityRole="button"
+          accessibilityLabel="Go back"
+          accessibilityHint="Navigates to the previous screen">
           <Text type="xl" style={[pal.link, s.pl5]}>
             Back
           </Text>
@@ -434,7 +467,10 @@ const LoginForm = ({
         {!serviceDescription && error ? (
           <TouchableOpacity
             testID="loginRetryButton"
-            onPress={onPressRetryConnect}>
+            onPress={onPressRetryConnect}
+            accessibilityRole="button"
+            accessibilityLabel="Retry"
+            accessibilityHint="Retries login">
             <Text type="xl-bold" style={[pal.link, s.pr5]}>
               Retry
             </Text>
@@ -449,7 +485,12 @@ const LoginForm = ({
         ) : isProcessing ? (
           <ActivityIndicator />
         ) : isReady ? (
-          <TouchableOpacity testID="loginNextButton" onPress={onPressNext}>
+          <TouchableOpacity
+            testID="loginNextButton"
+            onPress={onPressNext}
+            accessibilityRole="button"
+            accessibilityLabel="Go to next"
+            accessibilityHint="Navigates to the next screen">
             <Text type="xl-bold" style={[pal.link, s.pr5]}>
               Next
             </Text>
@@ -539,7 +580,10 @@ const ForgotPasswordForm = ({
           <TouchableOpacity
             testID="forgotPasswordSelectServiceButton"
             style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}
-            onPress={onPressSelectService}>
+            onPress={onPressSelectService}
+            accessibilityRole="button"
+            accessibilityLabel="Hosting provider"
+            accessibilityHint="Sets hosting provider for password reset">
             <FontAwesomeIcon
               icon="globe"
               style={[pal.textLight, styles.groupContentIcon]}
@@ -572,6 +616,8 @@ const ForgotPasswordForm = ({
               value={email}
               onChangeText={setEmail}
               editable={!isProcessing}
+              accessibilityLabel="Email"
+              accessibilityHint="Sets email for password reset"
             />
           </View>
         </View>
@@ -586,7 +632,11 @@ const ForgotPasswordForm = ({
           </View>
         ) : undefined}
         <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}>
-          <TouchableOpacity onPress={onPressBack}>
+          <TouchableOpacity
+            onPress={onPressBack}
+            accessibilityRole="button"
+            accessibilityLabel="Go back"
+            accessibilityHint="Navigates to the previous screen">
             <Text type="xl" style={[pal.link, s.pl5]}>
               Back
             </Text>
@@ -599,7 +649,12 @@ const ForgotPasswordForm = ({
               Next
             </Text>
           ) : (
-            <TouchableOpacity testID="newPasswordButton" onPress={onPressNext}>
+            <TouchableOpacity
+              testID="newPasswordButton"
+              onPress={onPressNext}
+              accessibilityRole="button"
+              accessibilityLabel="Go to next"
+              accessibilityHint="Navigates to the next screen">
               <Text type="xl-bold" style={[pal.link, s.pr5]}>
                 Next
               </Text>
@@ -699,6 +754,9 @@ const SetNewPasswordForm = ({
               value={resetCode}
               onChangeText={setResetCode}
               editable={!isProcessing}
+              accessible={true}
+              accessibilityLabel="Reset code"
+              accessibilityHint="Input code sent to your email for password reset"
             />
           </View>
           <View style={[pal.borderDark, styles.groupContent]}>
@@ -718,6 +776,9 @@ const SetNewPasswordForm = ({
               value={password}
               onChangeText={setPassword}
               editable={!isProcessing}
+              accessible={true}
+              accessibilityLabel="Password"
+              accessibilityHint="Input new password"
             />
           </View>
         </View>
@@ -732,7 +793,11 @@ const SetNewPasswordForm = ({
           </View>
         ) : undefined}
         <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}>
-          <TouchableOpacity onPress={onPressBack}>
+          <TouchableOpacity
+            onPress={onPressBack}
+            accessibilityRole="button"
+            accessibilityLabel="Go back"
+            accessibilityHint="Navigates to the previous screen">
             <Text type="xl" style={[pal.link, s.pl5]}>
               Back
             </Text>
@@ -747,7 +812,10 @@ const SetNewPasswordForm = ({
           ) : (
             <TouchableOpacity
               testID="setNewPasswordButton"
-              onPress={onPressNext}>
+              onPress={onPressNext}
+              accessibilityRole="button"
+              accessibilityLabel="Go to next"
+              accessibilityHint="Navigates to the next screen">
               <Text type="xl-bold" style={[pal.link, s.pr5]}>
                 Next
               </Text>
@@ -783,7 +851,11 @@ const PasswordUpdatedForm = ({onPressNext}: {onPressNext: () => void}) => {
         </Text>
         <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}>
           <View style={s.flex1} />
-          <TouchableOpacity onPress={onPressNext}>
+          <TouchableOpacity
+            onPress={onPressNext}
+            accessibilityRole="button"
+            accessibilityLabel="Close alert"
+            accessibilityHint="Closes password update alert">
             <Text type="xl-bold" style={[pal.link, s.pr5]}>
               Okay
             </Text>
diff --git a/src/view/com/auth/util/TextInput.tsx b/src/view/com/auth/util/TextInput.tsx
index 934bf2acf..38aff0384 100644
--- a/src/view/com/auth/util/TextInput.tsx
+++ b/src/view/com/auth/util/TextInput.tsx
@@ -1,27 +1,17 @@
-import React from 'react'
+import React, {ComponentProps} from 'react'
 import {StyleSheet, TextInput as RNTextInput, View} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {IconProp} from '@fortawesome/fontawesome-svg-core'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 
-export function TextInput({
-  testID,
-  icon,
-  value,
-  placeholder,
-  editable,
-  secureTextEntry,
-  onChange,
-}: {
+interface Props extends Omit<ComponentProps<typeof RNTextInput>, 'onChange'> {
   testID?: string
   icon: IconProp
-  value: string
-  placeholder: string
-  editable: boolean
-  secureTextEntry?: boolean
   onChange: (v: string) => void
-}) {
+}
+
+export function TextInput({testID, icon, onChange, ...props}: Props) {
   const theme = useTheme()
   const pal = usePalette('default')
   return (
@@ -30,15 +20,12 @@ export function TextInput({
       <RNTextInput
         testID={testID}
         style={[pal.text, styles.textInput]}
-        placeholder={placeholder}
         placeholderTextColor={pal.colors.textLight}
         autoCapitalize="none"
         autoCorrect={false}
         keyboardAppearance={theme.colorScheme}
-        secureTextEntry={secureTextEntry}
-        value={value}
         onChangeText={v => onChange(v)}
-        editable={editable}
+        {...props}
       />
     </View>
   )
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 5ccc229d6..45e67d7cb 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -7,7 +7,6 @@ import {
   ScrollView,
   StyleSheet,
   TouchableOpacity,
-  TouchableWithoutFeedback,
   View,
 } from 'react-native'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
@@ -19,6 +18,8 @@ import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
 import {ExternalEmbed} from './ExternalEmbed'
 import {Text} from '../util/text/Text'
 import * as Toast from '../util/Toast'
+// TODO: Prevent naming components that coincide with RN primitives
+// due to linting false positives
 import {TextInput, TextInputRef} from './text-input/TextInput'
 import {CharProgress} from './char-progress/CharProgress'
 import {UserAvatar} from '../util/UserAvatar'
@@ -87,27 +88,6 @@ export const ComposePost = observer(function ComposePost({
     autocompleteView.setup()
   }, [autocompleteView])
 
-  useEffect(() => {
-    // HACK
-    // wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
-    // -prf
-    let to: NodeJS.Timeout | undefined
-    if (textInput.current) {
-      to = setTimeout(() => {
-        textInput.current?.focus()
-      }, 250)
-    }
-    return () => {
-      if (to) {
-        clearTimeout(to)
-      }
-    }
-  }, [])
-
-  const onPressContainer = useCallback(() => {
-    textInput.current?.focus()
-  }, [textInput])
-
   const onPressAddLinkCard = useCallback(
     (uri: string) => {
       setExtLink({uri, isLoading: true})
@@ -133,7 +113,7 @@ export const ComposePost = observer(function ComposePost({
 
       if (rt.text.trim().length === 0 && gallery.isEmpty) {
         setError('Did you want to say anything?')
-        return false
+        return
       }
 
       setIsProcessing(true)
@@ -203,133 +183,149 @@ export const ComposePost = observer(function ComposePost({
       testID="composePostView"
       behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
       style={styles.outer}>
-      <TouchableWithoutFeedback onPressIn={onPressContainer}>
-        <View style={[s.flex1, viewStyles]}>
-          <View style={styles.topbar}>
+      <View style={[s.flex1, viewStyles]} aria-modal accessibilityViewIsModal>
+        <View style={styles.topbar}>
+          <TouchableOpacity
+            testID="composerCancelButton"
+            onPress={hackfixOnClose}
+            onAccessibilityEscape={hackfixOnClose}
+            accessibilityRole="button"
+            accessibilityLabel="Cancel"
+            accessibilityHint="Closes post composer">
+            <Text style={[pal.link, s.f18]}>Cancel</Text>
+          </TouchableOpacity>
+          <View style={s.flex1} />
+          {isProcessing ? (
+            <View style={styles.postBtn}>
+              <ActivityIndicator />
+            </View>
+          ) : canPost ? (
             <TouchableOpacity
-              testID="composerCancelButton"
-              onPress={hackfixOnClose}>
-              <Text style={[pal.link, s.f18]}>Cancel</Text>
+              testID="composerPublishBtn"
+              onPress={() => {
+                onPressPublish(richtext)
+              }}
+              accessibilityRole="button"
+              accessibilityLabel={replyTo ? 'Publish reply' : 'Publish post'}
+              accessibilityHint={
+                replyTo
+                  ? 'Double tap to publish your reply'
+                  : 'Double tap to publish your post'
+              }>
+              <LinearGradient
+                colors={[gradients.blueLight.start, gradients.blueLight.end]}
+                start={{x: 0, y: 0}}
+                end={{x: 1, y: 1}}
+                style={styles.postBtn}>
+                <Text style={[s.white, s.f16, s.bold]}>
+                  {replyTo ? 'Reply' : 'Post'}
+                </Text>
+              </LinearGradient>
             </TouchableOpacity>
-            <View style={s.flex1} />
-            {isProcessing ? (
-              <View style={styles.postBtn}>
-                <ActivityIndicator />
-              </View>
-            ) : canPost ? (
-              <TouchableOpacity
-                testID="composerPublishBtn"
-                onPress={() => {
-                  onPressPublish(richtext)
-                }}>
-                <LinearGradient
-                  colors={[gradients.blueLight.start, gradients.blueLight.end]}
-                  start={{x: 0, y: 0}}
-                  end={{x: 1, y: 1}}
-                  style={styles.postBtn}>
-                  <Text style={[s.white, s.f16, s.bold]}>
-                    {replyTo ? 'Reply' : 'Post'}
-                  </Text>
-                </LinearGradient>
-              </TouchableOpacity>
-            ) : (
-              <View style={[styles.postBtn, pal.btn]}>
-                <Text style={[pal.textLight, s.f16, s.bold]}>Post</Text>
-              </View>
-            )}
+          ) : (
+            <View style={[styles.postBtn, pal.btn]}>
+              <Text style={[pal.textLight, s.f16, s.bold]}>Post</Text>
+            </View>
+          )}
+        </View>
+        {isProcessing ? (
+          <View style={[pal.btn, styles.processingLine]}>
+            <Text style={pal.text}>{processingState}</Text>
           </View>
-          {isProcessing ? (
-            <View style={[pal.btn, styles.processingLine]}>
-              <Text style={pal.text}>{processingState}</Text>
+        ) : undefined}
+        {error !== '' && (
+          <View style={styles.errorLine}>
+            <View style={styles.errorIcon}>
+              <FontAwesomeIcon
+                icon="exclamation"
+                style={{color: colors.red4}}
+                size={10}
+              />
             </View>
-          ) : undefined}
-          {error !== '' && (
-            <View style={styles.errorLine}>
-              <View style={styles.errorIcon}>
-                <FontAwesomeIcon
-                  icon="exclamation"
-                  style={{color: colors.red4}}
-                  size={10}
-                />
+            <Text style={[s.red4, s.flex1]}>{error}</Text>
+          </View>
+        )}
+        <ScrollView
+          style={styles.scrollView}
+          keyboardShouldPersistTaps="always">
+          {replyTo ? (
+            <View style={[pal.border, styles.replyToLayout]}>
+              <UserAvatar avatar={replyTo.author.avatar} size={50} />
+              <View style={styles.replyToPost}>
+                <Text type="xl-medium" style={[pal.text]}>
+                  {sanitizeDisplayName(
+                    replyTo.author.displayName || replyTo.author.handle,
+                  )}
+                </Text>
+                <Text type="post-text" style={pal.text} numberOfLines={6}>
+                  {replyTo.text}
+                </Text>
               </View>
-              <Text style={[s.red4, s.flex1]}>{error}</Text>
             </View>
-          )}
-          <ScrollView
-            style={styles.scrollView}
-            keyboardShouldPersistTaps="always">
-            {replyTo ? (
-              <View style={[pal.border, styles.replyToLayout]}>
-                <UserAvatar avatar={replyTo.author.avatar} size={50} />
-                <View style={styles.replyToPost}>
-                  <Text type="xl-medium" style={[pal.text]}>
-                    {sanitizeDisplayName(
-                      replyTo.author.displayName || replyTo.author.handle,
-                    )}
-                  </Text>
-                  <Text type="post-text" style={pal.text} numberOfLines={6}>
-                    {replyTo.text}
-                  </Text>
-                </View>
-              </View>
-            ) : undefined}
+          ) : undefined}
 
-            <View style={[pal.border, styles.textInputLayout]}>
-              <UserAvatar avatar={store.me.avatar} size={50} />
-              <TextInput
-                ref={textInput}
-                richtext={richtext}
-                placeholder={selectTextInputPlaceholder}
-                suggestedLinks={suggestedLinks}
-                autocompleteView={autocompleteView}
-                setRichText={setRichText}
-                onPhotoPasted={onPhotoPasted}
-                onPressPublish={onPressPublish}
-                onSuggestedLinksChanged={setSuggestedLinks}
-                onError={setError}
-              />
-            </View>
+          <View style={[pal.border, styles.textInputLayout]}>
+            <UserAvatar avatar={store.me.avatar} size={50} />
+            <TextInput
+              ref={textInput}
+              richtext={richtext}
+              placeholder={selectTextInputPlaceholder}
+              suggestedLinks={suggestedLinks}
+              autocompleteView={autocompleteView}
+              autoFocus={true}
+              setRichText={setRichText}
+              onPhotoPasted={onPhotoPasted}
+              onPressPublish={onPressPublish}
+              onSuggestedLinksChanged={setSuggestedLinks}
+              onError={setError}
+              accessible={true}
+              accessibilityLabel="Write post"
+              accessibilityHint="Compose posts up to 300 characters in length"
+            />
+          </View>
 
-            <Gallery gallery={gallery} />
-            {gallery.isEmpty && extLink && (
-              <ExternalEmbed
-                link={extLink}
-                onRemove={() => setExtLink(undefined)}
-              />
-            )}
-            {quote ? (
-              <View style={s.mt5}>
-                <QuoteEmbed quote={quote} />
-              </View>
-            ) : undefined}
-          </ScrollView>
-          {!extLink && suggestedLinks.size > 0 ? (
-            <View style={s.mb5}>
-              {Array.from(suggestedLinks).map(url => (
-                <TouchableOpacity
-                  key={`suggested-${url}`}
-                  testID="addLinkCardBtn"
-                  style={[pal.borderDark, styles.addExtLinkBtn]}
-                  onPress={() => onPressAddLinkCard(url)}>
-                  <Text style={pal.text}>
-                    Add link card: <Text style={pal.link}>{url}</Text>
-                  </Text>
-                </TouchableOpacity>
-              ))}
+          <Gallery gallery={gallery} />
+          {gallery.isEmpty && extLink && (
+            <ExternalEmbed
+              link={extLink}
+              onRemove={() => setExtLink(undefined)}
+            />
+          )}
+          {quote ? (
+            <View style={s.mt5}>
+              <QuoteEmbed quote={quote} />
             </View>
-          ) : null}
-          <View style={[pal.border, styles.bottomBar]}>
-            {canSelectImages ? (
-              <>
-                <SelectPhotoBtn gallery={gallery} />
-                <OpenCameraBtn gallery={gallery} />
-              </>
-            ) : null}
-            <View style={s.flex1} />
-            <CharProgress count={graphemeLength} />
+          ) : undefined}
+        </ScrollView>
+        {!extLink && suggestedLinks.size > 0 ? (
+          <View style={s.mb5}>
+            {Array.from(suggestedLinks).map(url => (
+              <TouchableOpacity
+                key={`suggested-${url}`}
+                testID="addLinkCardBtn"
+                style={[pal.borderDark, styles.addExtLinkBtn]}
+                onPress={() => onPressAddLinkCard(url)}
+                accessibilityRole="button"
+                accessibilityLabel="Add link card"
+                accessibilityHint={`Creates a card with a thumbnail. The card links to ${url}`}>
+                <Text style={pal.text}>
+                  Add link card: <Text style={pal.link}>{url}</Text>
+                </Text>
+              </TouchableOpacity>
+            ))}
           </View>
+        ) : null}
+        <View style={[pal.border, styles.bottomBar]}>
+          {canSelectImages ? (
+            <>
+              <SelectPhotoBtn gallery={gallery} />
+              <OpenCameraBtn gallery={gallery} />
+            </>
+          ) : null}
+          <View style={s.flex1} />
+          <CharProgress count={graphemeLength} />
         </View>
-      </TouchableWithoutFeedback>
+      </View>
     </KeyboardAvoidingView>
   )
 })
diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx
index b6a45f6a3..a938562bd 100644
--- a/src/view/com/composer/ExternalEmbed.tsx
+++ b/src/view/com/composer/ExternalEmbed.tsx
@@ -60,7 +60,13 @@ export const ExternalEmbed = ({
           </Text>
         )}
       </View>
-      <TouchableOpacity style={styles.removeBtn} onPress={onRemove}>
+      <TouchableOpacity
+        style={styles.removeBtn}
+        onPress={onRemove}
+        accessibilityRole="button"
+        accessibilityLabel="Remove image preview"
+        accessibilityHint={`Removes default thumbnail from ${link.uri}`}
+        onAccessibilityEscape={onRemove}>
         <FontAwesomeIcon size={18} icon="xmark" style={s.white} />
       </TouchableOpacity>
     </View>
diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx
index 301b90093..98a10b0f5 100644
--- a/src/view/com/composer/Prompt.tsx
+++ b/src/view/com/composer/Prompt.tsx
@@ -13,7 +13,10 @@ export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
     <TouchableOpacity
       testID="replyPromptBtn"
       style={[pal.view, pal.border, styles.prompt]}
-      onPress={() => onPressCompose()}>
+      onPress={() => onPressCompose()}
+      accessibilityRole="button"
+      accessibilityLabel="Compose reply"
+      accessibilityHint="Opens composer">
       <UserAvatar avatar={store.me.avatar} size={38} />
       <Text
         type="xl"
diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx
index 98f0824fd..e2d95b2a4 100644
--- a/src/view/com/composer/photos/Gallery.tsx
+++ b/src/view/com/composer/photos/Gallery.tsx
@@ -107,6 +107,9 @@ export const Gallery = observer(function ({gallery}: Props) {
           <View key={`selected-image-${image.path}`} style={[imageStyle]}>
             <TouchableOpacity
               testID="altTextButton"
+              accessibilityRole="button"
+              accessibilityLabel="Add alt text"
+              accessibilityHint="Opens modal for inputting image alt text"
               onPress={() => {
                 handleAddImageAltText(image)
               }}
@@ -116,6 +119,9 @@ export const Gallery = observer(function ({gallery}: Props) {
             <View style={imageControlsSubgroupStyle}>
               <TouchableOpacity
                 testID="cropPhotoButton"
+                accessibilityRole="button"
+                accessibilityLabel="Crop image"
+                accessibilityHint="Opens modal for cropping image"
                 onPress={() => {
                   handleEditPhoto(image)
                 }}
@@ -128,6 +134,9 @@ export const Gallery = observer(function ({gallery}: Props) {
               </TouchableOpacity>
               <TouchableOpacity
                 testID="removePhotoButton"
+                accessibilityRole="button"
+                accessibilityLabel="Remove image"
+                accessibilityHint=""
                 onPress={() => handleRemovePhoto(image)}
                 style={styles.imageControl}>
                 <FontAwesomeIcon
@@ -144,6 +153,8 @@ export const Gallery = observer(function ({gallery}: Props) {
               source={{
                 uri: image.compressed.path,
               }}
+              accessible={true}
+              accessibilityIgnoresInvertColors
             />
           </View>
         ) : null,
diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx
index 809c41783..bfcfa6b78 100644
--- a/src/view/com/composer/photos/OpenCameraBtn.tsx
+++ b/src/view/com/composer/photos/OpenCameraBtn.tsx
@@ -1,5 +1,5 @@
 import React, {useCallback} from 'react'
-import {TouchableOpacity} from 'react-native'
+import {TouchableOpacity, StyleSheet} from 'react-native'
 import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
@@ -7,7 +7,6 @@ import {
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnalytics} from 'lib/analytics'
 import {useStores} from 'state/index'
-import {s} from 'lib/styles'
 import {isDesktopWeb} from 'platform/detection'
 import {openCamera} from 'lib/media/picker'
 import {useCameraPermission} from 'lib/hooks/usePermissions'
@@ -54,8 +53,11 @@ export function OpenCameraBtn({gallery}: Props) {
     <TouchableOpacity
       testID="openCameraButton"
       onPress={onPressTakePicture}
-      style={[s.pl5]}
-      hitSlop={HITSLOP}>
+      style={styles.button}
+      hitSlop={HITSLOP}
+      accessibilityRole="button"
+      accessibilityLabel="Camera"
+      accessibilityHint="Opens camera on device">
       <FontAwesomeIcon
         icon="camera"
         style={pal.link as FontAwesomeIconStyle}
@@ -64,3 +66,9 @@ export function OpenCameraBtn({gallery}: Props) {
     </TouchableOpacity>
   )
 }
+
+const styles = StyleSheet.create({
+  button: {
+    paddingHorizontal: 15,
+  },
+})
diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx
index 9569e08ad..0b8046a4b 100644
--- a/src/view/com/composer/photos/SelectPhotoBtn.tsx
+++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx
@@ -1,12 +1,11 @@
 import React, {useCallback} from 'react'
-import {TouchableOpacity} from 'react-native'
+import {TouchableOpacity, StyleSheet} from 'react-native'
 import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
 } from '@fortawesome/react-native-fontawesome'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnalytics} from 'lib/analytics'
-import {s} from 'lib/styles'
 import {isDesktopWeb} from 'platform/detection'
 import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
 import {GalleryModel} from 'state/models/media/gallery'
@@ -36,8 +35,11 @@ export function SelectPhotoBtn({gallery}: Props) {
     <TouchableOpacity
       testID="openGalleryBtn"
       onPress={onPressSelectPhotos}
-      style={[s.pl5, s.pr20]}
-      hitSlop={HITSLOP}>
+      style={styles.button}
+      hitSlop={HITSLOP}
+      accessibilityRole="button"
+      accessibilityLabel="Gallery"
+      accessibilityHint="Opens device photo gallery">
       <FontAwesomeIcon
         icon={['far', 'image']}
         style={pal.link as FontAwesomeIconStyle}
@@ -46,3 +48,9 @@ export function SelectPhotoBtn({gallery}: Props) {
     </TouchableOpacity>
   )
 }
+
+const styles = StyleSheet.create({
+  button: {
+    paddingHorizontal: 15,
+  },
+})
diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx
index 10ac52b5d..7b09da93d 100644
--- a/src/view/com/composer/text-input/TextInput.tsx
+++ b/src/view/com/composer/text-input/TextInput.tsx
@@ -1,7 +1,14 @@
-import React, {forwardRef, useCallback, useEffect, useRef, useMemo} from 'react'
+import React, {
+  forwardRef,
+  useCallback,
+  useRef,
+  useMemo,
+  ComponentProps,
+} from 'react'
 import {
   NativeSyntheticEvent,
   StyleSheet,
+  TextInput as RNTextInput,
   TextInputSelectionChangeEventData,
   View,
 } from 'react-native'
@@ -27,14 +34,14 @@ export interface TextInputRef {
   blur: () => void
 }
 
-interface TextInputProps {
+interface TextInputProps extends ComponentProps<typeof RNTextInput> {
   richtext: RichText
   placeholder: string
   suggestedLinks: Set<string>
   autocompleteView: UserAutocompleteModel
-  setRichText: (v: RichText) => void
+  setRichText: (v: RichText | ((v: RichText) => RichText)) => void
   onPhotoPasted: (uri: string) => void
-  onPressPublish: (richtext: RichText) => Promise<false | undefined>
+  onPressPublish: (richtext: RichText) => Promise<void>
   onSuggestedLinksChanged: (uris: Set<string>) => void
   onError: (err: string) => void
 }
@@ -55,6 +62,7 @@ export const TextInput = forwardRef(
       onPhotoPasted,
       onSuggestedLinksChanged,
       onError,
+      ...props
     }: TextInputProps,
     ref,
   ) => {
@@ -65,26 +73,11 @@ export const TextInput = forwardRef(
 
     React.useImperativeHandle(ref, () => ({
       focus: () => textInput.current?.focus(),
-      blur: () => textInput.current?.blur(),
+      blur: () => {
+        textInput.current?.blur()
+      },
     }))
 
-    useEffect(() => {
-      // HACK
-      // wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
-      // -prf
-      let to: NodeJS.Timeout | undefined
-      if (textInput.current) {
-        to = setTimeout(() => {
-          textInput.current?.focus()
-        }, 250)
-      }
-      return () => {
-        if (to) {
-          clearTimeout(to)
-        }
-      }
-    }, [])
-
     const onChangeText = useCallback(
       async (newText: string) => {
         const newRt = new RichText({text: newText})
@@ -206,8 +199,10 @@ export const TextInput = forwardRef(
           placeholder={placeholder}
           placeholderTextColor={pal.colors.textLight}
           keyboardAppearance={theme.colorScheme}
+          autoFocus={true}
           multiline
-          style={[pal.text, styles.textInput, styles.textInputFormatting]}>
+          style={[pal.text, styles.textInput, styles.textInputFormatting]}
+          {...props}>
           {textDecorated}
         </PasteInput>
         <Autocomplete
diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx
index 3f98a3595..4abedb3e2 100644
--- a/src/view/com/composer/text-input/TextInput.web.tsx
+++ b/src/view/com/composer/text-input/TextInput.web.tsx
@@ -25,9 +25,9 @@ interface TextInputProps {
   placeholder: string
   suggestedLinks: Set<string>
   autocompleteView: UserAutocompleteModel
-  setRichText: (v: RichText) => void
+  setRichText: (v: RichText | ((v: RichText) => RichText)) => void
   onPhotoPasted: (uri: string) => void
-  onPressPublish: (richtext: RichText) => Promise<false | undefined>
+  onPressPublish: (richtext: RichText) => Promise<void>
   onSuggestedLinksChanged: (uris: Set<string>) => void
   onError: (err: string) => void
 }
diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx
index 879bac071..7806241f1 100644
--- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx
+++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx
@@ -50,7 +50,9 @@ export const Autocomplete = observer(
               testID="autocompleteButton"
               key={item.handle}
               style={[pal.border, styles.item]}
-              onPress={() => onSelect(item.handle)}>
+              onPress={() => onSelect(item.handle)}
+              accessibilityLabel={`Select ${item.handle}`}
+              accessibilityHint={`Autocompletes to ${item.handle}`}>
               <Text type="md-medium" style={pal.text}>
                 {item.displayName || item.handle}
                 <Text type="sm" style={pal.textLight}>
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx b/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx
index 6880008e4..84e5f90fb 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx
@@ -20,7 +20,11 @@ const ImageDefaultHeader = ({onRequestClose}: Props) => (
     <TouchableOpacity
       style={styles.closeButton}
       onPress={onRequestClose}
-      hitSlop={HIT_SLOP}>
+      hitSlop={HIT_SLOP}
+      accessibilityRole="button"
+      accessibilityLabel="Close image"
+      accessibilityHint="Closes viewer for header image"
+      onAccessibilityEscape={onRequestClose}>
       <Text style={styles.closeText}>✕</Text>
     </TouchableOpacity>
   </SafeAreaView>
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
index 12d37e283..658735724 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
@@ -127,7 +127,8 @@ const ImageItem = ({
         <TouchableWithoutFeedback
           onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined}
           onLongPress={onLongPressHandler}
-          delayLongPress={delayLongPress}>
+          delayLongPress={delayLongPress}
+          accessibilityRole="image">
           <Animated.Image
             source={imageSrc}
             style={imageStylesWithOpacity}
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index c10d57b37..531df129e 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -112,7 +112,12 @@ function ImageViewing({
   }
 
   return (
-    <SafeAreaView style={styles.screen} onLayout={onLayout} edges={edges}>
+    <SafeAreaView
+      style={styles.screen}
+      onLayout={onLayout}
+      edges={edges}
+      aria-modal
+      accessibilityViewIsModal>
       <ModalsContainer />
       <View style={[styles.container, {opacity, backgroundColor}]}>
         <Animated.View style={[styles.header, {transform: headerTransform}]}>
diff --git a/src/view/com/lightbox/Lightbox.web.tsx b/src/view/com/lightbox/Lightbox.web.tsx
index c17356d94..1d4a9c215 100644
--- a/src/view/com/lightbox/Lightbox.web.tsx
+++ b/src/view/com/lightbox/Lightbox.web.tsx
@@ -89,13 +89,25 @@ function LightboxInner({
 
   return (
     <View style={styles.mask}>
-      <TouchableWithoutFeedback onPress={onClose}>
+      <TouchableWithoutFeedback
+        onPress={onClose}
+        accessibilityRole="button"
+        accessibilityLabel="Close image viewer"
+        accessibilityHint="Exits image view"
+        onAccessibilityEscape={onClose}>
         <View style={styles.imageCenterer}>
-          <Image source={imgs[index]} style={styles.image} />
+          <Image
+            accessibilityIgnoresInvertColors
+            source={imgs[index]}
+            style={styles.image}
+          />
           {canGoLeft && (
             <TouchableOpacity
               onPress={onPressLeft}
-              style={[styles.btn, styles.leftBtn]}>
+              style={[styles.btn, styles.leftBtn]}
+              accessibilityRole="button"
+              accessibilityLabel="Go back"
+              accessibilityHint="Navigates to previous image in viewer">
               <FontAwesomeIcon
                 icon="angle-left"
                 style={styles.icon}
@@ -106,7 +118,10 @@ function LightboxInner({
           {canGoRight && (
             <TouchableOpacity
               onPress={onPressRight}
-              style={[styles.btn, styles.rightBtn]}>
+              style={[styles.btn, styles.rightBtn]}
+              accessibilityRole="button"
+              accessibilityLabel="Go to next"
+              accessibilityHint="Navigates to next image in viewer">
               <FontAwesomeIcon
                 icon="angle-right"
                 style={styles.icon}
diff --git a/src/view/com/modals/AddAppPasswords.tsx b/src/view/com/modals/AddAppPasswords.tsx
index 89ae81f5e..58b53586b 100644
--- a/src/view/com/modals/AddAppPasswords.tsx
+++ b/src/view/com/modals/AddAppPasswords.tsx
@@ -122,12 +122,18 @@ export function Component({}: {}) {
               editable={!appPassword}
               returnKeyType="done"
               onEndEditing={createAppPassword}
+              accessible={true}
+              accessibilityLabel="Name"
+              accessibilityHint="Input name for app password"
             />
           </View>
         ) : (
           <TouchableOpacity
             style={[pal.border, styles.passwordContainer, pal.btn]}
-            onPress={onCopy}>
+            onPress={onCopy}
+            accessibilityRole="button"
+            accessibilityLabel="Copy"
+            accessibilityHint="Copies app password">
             <Text type="2xl-bold" style={[pal.text]}>
               {appPassword}
             </Text>
diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx
index 639303c98..ba05a7d62 100644
--- a/src/view/com/modals/AltImage.tsx
+++ b/src/view/com/modals/AltImage.tsx
@@ -37,7 +37,8 @@ export function Component({prevAltText, onAltTextSet}: Props) {
   return (
     <View
       testID="altTextImageModal"
-      style={[pal.view, styles.container, s.flex1]}>
+      style={[pal.view, styles.container, s.flex1]}
+      nativeID="imageAltText">
       <Text style={[styles.title, pal.text]}>Add alt text</Text>
       <TextInput
         testID="altTextImageInput"
@@ -46,9 +47,17 @@ export function Component({prevAltText, onAltTextSet}: Props) {
         multiline
         value={altText}
         onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))}
+        accessibilityLabel="Image alt text"
+        accessibilityHint="Sets image alt text for screenreaders"
+        accessibilityLabelledBy="imageAltText"
       />
       <View style={styles.buttonControls}>
-        <TouchableOpacity testID="altTextImageSaveBtn" onPress={onPressSave}>
+        <TouchableOpacity
+          testID="altTextImageSaveBtn"
+          onPress={onPressSave}
+          accessibilityLabel="Save alt text"
+          accessibilityHint={`Saves alt text, which reads: ${altText}`}
+          accessibilityRole="button">
           <LinearGradient
             colors={[gradients.blueLight.start, gradients.blueLight.end]}
             start={{x: 0, y: 0}}
@@ -61,7 +70,11 @@ export function Component({prevAltText, onAltTextSet}: Props) {
         </TouchableOpacity>
         <TouchableOpacity
           testID="altTextImageCancelBtn"
-          onPress={onPressCancel}>
+          onPress={onPressCancel}
+          accessibilityRole="button"
+          accessibilityLabel="Cancel add image alt text"
+          accessibilityHint="Exits adding alt text to image"
+          onAccessibilityEscape={onPressCancel}>
           <View style={[styles.button]}>
             <Text type="button-lg" style={[pal.textLight]}>
               Cancel
diff --git a/src/view/com/modals/AltImageRead.tsx b/src/view/com/modals/AltImageRead.tsx
index e7b4797ee..4dde8f58b 100644
--- a/src/view/com/modals/AltImageRead.tsx
+++ b/src/view/com/modals/AltImageRead.tsx
@@ -30,7 +30,12 @@ export function Component({altText}: Props) {
       <View style={[styles.text, pal.viewLight]}>
         <Text style={pal.text}>{altText}</Text>
       </View>
-      <TouchableOpacity testID="altTextImageSaveBtn" onPress={onPress}>
+      <TouchableOpacity
+        testID="altTextImageSaveBtn"
+        onPress={onPress}
+        accessibilityRole="button"
+        accessibilityLabel="Save"
+        accessibilityHint="Save alt text">
         <LinearGradient
           colors={[gradients.blueLight.start, gradients.blueLight.end]}
           start={{x: 0, y: 0}}
diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx
index 37bad6957..ad7ff5a6d 100644
--- a/src/view/com/modals/ChangeHandle.tsx
+++ b/src/view/com/modals/ChangeHandle.tsx
@@ -133,7 +133,12 @@ export function Component({onChanged}: {onChanged: () => void}) {
     <View style={[s.flex1, pal.view]}>
       <View style={[styles.title, pal.border]}>
         <View style={styles.titleLeft}>
-          <TouchableOpacity onPress={onPressCancel}>
+          <TouchableOpacity
+            onPress={onPressCancel}
+            accessibilityRole="button"
+            accessibilityLabel="Cancel change handle"
+            accessibilityHint="Exits handle change process"
+            onAccessibilityEscape={onPressCancel}>
             <Text type="lg" style={pal.textLight}>
               Cancel
             </Text>
@@ -148,13 +153,20 @@ export function Component({onChanged}: {onChanged: () => void}) {
           ) : error && !serviceDescription ? (
             <TouchableOpacity
               testID="retryConnectButton"
-              onPress={onPressRetryConnect}>
+              onPress={onPressRetryConnect}
+              accessibilityRole="button"
+              accessibilityLabel="Retry change handle"
+              accessibilityHint={`Retries handle change to ${handle}`}>
               <Text type="xl-bold" style={[pal.link, s.pr5]}>
                 Retry
               </Text>
             </TouchableOpacity>
           ) : canSave ? (
-            <TouchableOpacity onPress={onPressSave}>
+            <TouchableOpacity
+              onPress={onPressSave}
+              accessibilityRole="button"
+              accessibilityLabel="Save handle change"
+              accessibilityHint={`Saves handle change to ${handle}`}>
               <Text type="2xl-medium" style={pal.link}>
                 Save
               </Text>
@@ -245,6 +257,9 @@ function ProvidedHandleForm({
           value={handle}
           onChangeText={onChangeHandle}
           editable={!isProcessing}
+          accessible={true}
+          accessibilityLabel="Handle"
+          accessibilityHint="Sets Bluesky username"
         />
       </View>
       <Text type="md" style={[pal.textLight, s.pl10, s.pt10]}>
@@ -253,7 +268,11 @@ function ProvidedHandleForm({
           @{createFullHandle(handle, userDomain)}
         </Text>
       </Text>
-      <TouchableOpacity onPress={onToggleCustom}>
+      <TouchableOpacity
+        onPress={onToggleCustom}
+        accessibilityRole="button"
+        accessibilityHint="Hosting provider"
+        accessibilityLabel="Opens modal for using custom domain">
         <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
           I have my own domain
         </Text>
@@ -338,7 +357,7 @@ function CustomHandleForm({
   // =
   return (
     <>
-      <Text type="md" style={[pal.text, s.pb5, s.pl5]}>
+      <Text type="md" style={[pal.text, s.pb5, s.pl5]} nativeID="customDomain">
         Enter the domain you want to use
       </Text>
       <View style={[pal.btn, styles.textInputWrapper]}>
@@ -356,6 +375,9 @@ function CustomHandleForm({
           value={handle}
           onChangeText={onChangeHandle}
           editable={!isProcessing}
+          accessibilityLabelledBy="customDomain"
+          accessibilityLabel="Custom domain"
+          accessibilityHint="Input your preferred hosting provider"
         />
       </View>
       <View style={styles.spacer} />
@@ -421,7 +443,10 @@ function CustomHandleForm({
         )}
       </Button>
       <View style={styles.spacer} />
-      <TouchableOpacity onPress={onToggleCustom}>
+      <TouchableOpacity
+        onPress={onToggleCustom}
+        accessibilityLabel="Use default provider"
+        accessibilityHint="Use bsky.social as hosting provider">
         <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
           Nevermind, create a handle for me
         </Text>
diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx
index 6f7b062cf..f0c905d04 100644
--- a/src/view/com/modals/Confirm.tsx
+++ b/src/view/com/modals/Confirm.tsx
@@ -66,7 +66,12 @@ export function Component({
         <TouchableOpacity
           testID="confirmBtn"
           onPress={onPress}
-          style={[styles.btn]}>
+          style={[styles.btn]}
+          accessibilityRole="button"
+          accessibilityLabel="Confirm"
+          // TODO: This needs to be updated so that modal roles are clear;
+          // Currently there is only one usage for the confirm modal: post deletion
+          accessibilityHint="Confirms a potentially destructive action">
           <Text style={[s.white, s.bold, s.f18]}>Confirm</Text>
         </TouchableOpacity>
       )}
diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx
index 735de85a7..c683e43f8 100644
--- a/src/view/com/modals/ContentFilteringSettings.tsx
+++ b/src/view/com/modals/ContentFilteringSettings.tsx
@@ -34,7 +34,12 @@ export function Component({}: {}) {
         <View style={styles.bottomSpacer} />
       </ScrollView>
       <View style={[styles.btnContainer, pal.borderDark]}>
-        <Pressable testID="sendReportBtn" onPress={onPressDone}>
+        <Pressable
+          testID="sendReportBtn"
+          onPress={onPressDone}
+          accessibilityRole="button"
+          accessibilityLabel="Confirm content moderation settings"
+          accessibilityHint="">
           <LinearGradient
             colors={[gradients.blueLight.start, gradients.blueLight.end]}
             start={{x: 0, y: 0}}
@@ -48,6 +53,7 @@ export function Component({}: {}) {
   )
 }
 
+// TODO: Refactor this component to pass labels down to each tab
 const ContentLabelPref = observer(
   ({group}: {group: keyof typeof CONFIGURABLE_LABEL_GROUPS}) => {
     const store = useStores()
@@ -67,19 +73,20 @@ const ContentLabelPref = observer(
         <SelectGroup
           current={store.preferences.contentLabels[group]}
           onChange={v => store.preferences.setContentLabelPref(group, v)}
+          group={group}
         />
       </View>
     )
   },
 )
 
-function SelectGroup({
-  current,
-  onChange,
-}: {
+interface SelectGroupProps {
   current: LabelPreference
   onChange: (v: LabelPreference) => void
-}) {
+  group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+}
+
+function SelectGroup({current, onChange, group}: SelectGroupProps) {
   return (
     <View style={styles.selectableBtns}>
       <SelectableBtn
@@ -88,12 +95,14 @@ function SelectGroup({
         label="Hide"
         left
         onChange={onChange}
+        group={group}
       />
       <SelectableBtn
         current={current}
         value="warn"
         label="Warn"
         onChange={onChange}
+        group={group}
       />
       <SelectableBtn
         current={current}
@@ -101,11 +110,22 @@ function SelectGroup({
         label="Show"
         right
         onChange={onChange}
+        group={group}
       />
     </View>
   )
 }
 
+interface SelectableBtnProps {
+  current: string
+  value: LabelPreference
+  label: string
+  left?: boolean
+  right?: boolean
+  onChange: (v: LabelPreference) => void
+  group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+}
+
 function SelectableBtn({
   current,
   value,
@@ -113,14 +133,8 @@ function SelectableBtn({
   left,
   right,
   onChange,
-}: {
-  current: string
-  value: LabelPreference
-  label: string
-  left?: boolean
-  right?: boolean
-  onChange: (v: LabelPreference) => void
-}) {
+  group,
+}: SelectableBtnProps) {
   const pal = usePalette('default')
   const palPrimary = usePalette('inverted')
   return (
@@ -132,7 +146,10 @@ function SelectableBtn({
         pal.border,
         current === value ? palPrimary.view : pal.view,
       ]}
-      onPress={() => onChange(value)}>
+      onPress={() => onChange(value)}
+      accessibilityRole="button"
+      accessibilityLabel={value}
+      accessibilityHint={`Set ${value} for ${group} content moderation policy`}>
       <Text style={current === value ? palPrimary.text : pal.text}>
         {label}
       </Text>
diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx
index 353122163..f1febc2ea 100644
--- a/src/view/com/modals/DeleteAccount.tsx
+++ b/src/view/com/modals/DeleteAccount.tsx
@@ -86,7 +86,10 @@ export function Component({}: {}) {
               <>
                 <TouchableOpacity
                   style={styles.mt20}
-                  onPress={onPressSendEmail}>
+                  onPress={onPressSendEmail}
+                  accessibilityRole="button"
+                  accessibilityLabel="Send email"
+                  accessibilityHint="Sends email with confirmation code for account deletion">
                   <LinearGradient
                     colors={[
                       gradients.blueLight.start,
@@ -102,7 +105,11 @@ export function Component({}: {}) {
                 </TouchableOpacity>
                 <TouchableOpacity
                   style={[styles.btn, s.mt10]}
-                  onPress={onCancel}>
+                  onPress={onCancel}
+                  accessibilityRole="button"
+                  accessibilityLabel="Cancel account deletion"
+                  accessibilityHint=""
+                  onAccessibilityEscape={onCancel}>
                   <Text type="button-lg" style={pal.textLight}>
                     Cancel
                   </Text>
@@ -112,7 +119,11 @@ export function Component({}: {}) {
           </>
         ) : (
           <>
-            <Text type="lg" style={styles.description}>
+            {/* TODO: Update this label to be more concise */}
+            <Text
+              type="lg"
+              style={styles.description}
+              nativeID="confirmationCode">
               Check your inbox for an email with the confirmation code to enter
               below:
             </Text>
@@ -123,8 +134,11 @@ export function Component({}: {}) {
               keyboardAppearance={theme.colorScheme}
               value={confirmCode}
               onChangeText={setConfirmCode}
+              accessibilityLabelledBy="confirmationCode"
+              accessibilityLabel="Confirmation code"
+              accessibilityHint="Input confirmation code for account deletion"
             />
-            <Text type="lg" style={styles.description}>
+            <Text type="lg" style={styles.description} nativeID="password">
               Please enter your password as well:
             </Text>
             <TextInput
@@ -135,6 +149,9 @@ export function Component({}: {}) {
               secureTextEntry
               value={password}
               onChangeText={setPassword}
+              accessibilityLabelledBy="password"
+              accessibilityLabel="Password"
+              accessibilityHint="Input password for account deletion"
             />
             {error ? (
               <View style={styles.mt20}>
@@ -149,14 +166,21 @@ export function Component({}: {}) {
               <>
                 <TouchableOpacity
                   style={[styles.btn, styles.evilBtn, styles.mt20]}
-                  onPress={onPressConfirmDelete}>
+                  onPress={onPressConfirmDelete}
+                  accessibilityRole="button"
+                  accessibilityLabel="Confirm delete account"
+                  accessibilityHint="">
                   <Text type="button-lg" style={[s.white, s.bold]}>
                     Delete my account
                   </Text>
                 </TouchableOpacity>
                 <TouchableOpacity
                   style={[styles.btn, s.mt10]}
-                  onPress={onCancel}>
+                  onPress={onCancel}
+                  accessibilityRole="button"
+                  accessibilityLabel="Cancel account deletion"
+                  accessibilityHint="Exits account deletion process"
+                  onAccessibilityEscape={onCancel}>
                   <Text type="button-lg" style={pal.textLight}>
                     Cancel
                   </Text>
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index 9bd572cc0..c26592fa9 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -175,6 +175,9 @@ export function Component({
               onChangeText={v =>
                 setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))
               }
+              accessible={true}
+              accessibilityLabel="Display name"
+              accessibilityHint="Edit your display name"
             />
           </View>
           <View style={s.pb10}>
@@ -188,6 +191,9 @@ export function Component({
               multiline
               value={description}
               onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
+              accessible={true}
+              accessibilityLabel="Description"
+              accessibilityHint="Edit your profile description"
             />
           </View>
           {isProcessing ? (
@@ -198,7 +204,10 @@ export function Component({
             <TouchableOpacity
               testID="editProfileSaveBtn"
               style={s.mt10}
-              onPress={onPressSave}>
+              onPress={onPressSave}
+              accessibilityRole="button"
+              accessibilityLabel="Save"
+              accessibilityHint="Saves any changes to your profile">
               <LinearGradient
                 colors={[gradients.blueLight.start, gradients.blueLight.end]}
                 start={{x: 0, y: 0}}
@@ -211,7 +220,11 @@ export function Component({
           <TouchableOpacity
             testID="editProfileCancelBtn"
             style={s.mt5}
-            onPress={onPressCancel}>
+            onPress={onPressCancel}
+            accessibilityRole="button"
+            accessibilityLabel="Cancel profile editing"
+            accessibilityHint=""
+            onAccessibilityEscape={onPressCancel}>
             <View style={[styles.btn]}>
               <Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
             </View>
diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx
index 992439ebc..52d6fa46a 100644
--- a/src/view/com/modals/InviteCodes.tsx
+++ b/src/view/com/modals/InviteCodes.tsx
@@ -87,6 +87,7 @@ const InviteCode = observer(
   ({testID, code, used}: {testID: string; code: string; used?: boolean}) => {
     const pal = usePalette('default')
     const store = useStores()
+    const {invitesAvailable} = store.me
 
     const onPress = React.useCallback(() => {
       Clipboard.setString(code)
@@ -98,7 +99,14 @@ const InviteCode = observer(
       <TouchableOpacity
         testID={testID}
         style={[styles.inviteCode, pal.border]}
-        onPress={onPress}>
+        onPress={onPress}
+        accessibilityRole="button"
+        accessibilityLabel={
+          invitesAvailable === 1
+            ? 'Invite codes: 1 available'
+            : `Invite codes: ${invitesAvailable} available`
+        }
+        accessibilityHint="Opens list of invite codes">
         <Text
           testID={`${testID}-code`}
           type={used ? 'md' : 'md-bold'}
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index de748b3a8..e850c9f21 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -53,6 +53,7 @@ function Modal({modal}: {modal: ModalIface}) {
     store.shell.closeModal()
   }
   const onInnerPress = () => {
+    // TODO: can we use prevent default?
     // do nothing, we just want to stop it from bubbling
   }
 
@@ -92,8 +93,10 @@ function Modal({modal}: {modal: ModalIface}) {
   }
 
   return (
+    // eslint-disable-next-line
     <TouchableWithoutFeedback onPress={onPressMask}>
       <View style={styles.mask}>
+        {/* eslint-disable-next-line */}
         <TouchableWithoutFeedback onPress={onInnerPress}>
           <View
             style={[
diff --git a/src/view/com/modals/ReportAccount.tsx b/src/view/com/modals/ReportAccount.tsx
index e03f06bde..b59a1b699 100644
--- a/src/view/com/modals/ReportAccount.tsx
+++ b/src/view/com/modals/ReportAccount.tsx
@@ -110,7 +110,10 @@ export function Component({did}: {did: string}) {
         <TouchableOpacity
           testID="sendReportBtn"
           style={s.mt10}
-          onPress={onPress}>
+          onPress={onPress}
+          accessibilityRole="button"
+          accessibilityLabel="Report account"
+          accessibilityHint={`Reports account with reason ${issue}`}>
           <LinearGradient
             colors={[gradients.blueLight.start, gradients.blueLight.end]}
             start={{x: 0, y: 0}}
diff --git a/src/view/com/modals/ReportPost.tsx b/src/view/com/modals/ReportPost.tsx
index c2c89202b..0695eed8e 100644
--- a/src/view/com/modals/ReportPost.tsx
+++ b/src/view/com/modals/ReportPost.tsx
@@ -153,7 +153,10 @@ export function Component({
         <TouchableOpacity
           testID="sendReportBtn"
           style={s.mt10}
-          onPress={onPress}>
+          onPress={onPress}
+          accessibilityRole="button"
+          accessibilityLabel="Report post"
+          accessibilityHint={`Reports post with reason ${issue}`}>
           <LinearGradient
             colors={[gradients.blueLight.start, gradients.blueLight.end]}
             start={{x: 0, y: 0}}
diff --git a/src/view/com/modals/Repost.tsx b/src/view/com/modals/Repost.tsx
index d5ed66b70..b1862ecbd 100644
--- a/src/view/com/modals/Repost.tsx
+++ b/src/view/com/modals/Repost.tsx
@@ -18,6 +18,7 @@ export function Component({
   onRepost: () => void
   onQuote: () => void
   isReposted: boolean
+  // TODO: Add author into component
 }) {
   const store = useStores()
   const pal = usePalette('default')
@@ -31,7 +32,10 @@ export function Component({
         <TouchableOpacity
           testID="repostBtn"
           style={[styles.actionBtn]}
-          onPress={onRepost}>
+          onPress={onRepost}
+          accessibilityRole="button"
+          accessibilityLabel={isReposted ? 'Undo repost' : 'Repost'}
+          accessibilityHint={isReposted ? 'Remove repost' : 'Repost '}>
           <RepostIcon strokeWidth={2} size={24} style={s.blue3} />
           <Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}>
             {!isReposted ? 'Repost' : 'Undo repost'}
@@ -40,14 +44,23 @@ export function Component({
         <TouchableOpacity
           testID="quoteBtn"
           style={[styles.actionBtn]}
-          onPress={onQuote}>
+          onPress={onQuote}
+          accessibilityRole="button"
+          accessibilityLabel="Quote post"
+          accessibilityHint="">
           <FontAwesomeIcon icon="quote-left" size={24} style={s.blue3} />
           <Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}>
             Quote Post
           </Text>
         </TouchableOpacity>
       </View>
-      <TouchableOpacity testID="cancelBtn" onPress={onPress}>
+      <TouchableOpacity
+        testID="cancelBtn"
+        onPress={onPress}
+        accessibilityRole="button"
+        accessibilityLabel="Cancel quote post"
+        accessibilityHint=""
+        onAccessibilityEscape={onPress}>
         <LinearGradient
           colors={[gradients.blueLight.start, gradients.blueLight.end]}
           start={{x: 0, y: 0}}
diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx
index 078abbf64..13b21fe22 100644
--- a/src/view/com/modals/ServerInput.tsx
+++ b/src/view/com/modals/ServerInput.tsx
@@ -41,7 +41,8 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
               <TouchableOpacity
                 testID="localDevServerButton"
                 style={styles.btn}
-                onPress={() => doSelect(LOCAL_DEV_SERVICE)}>
+                onPress={() => doSelect(LOCAL_DEV_SERVICE)}
+                accessibilityRole="button">
                 <Text style={styles.btnText}>Local dev server</Text>
                 <FontAwesomeIcon
                   icon="arrow-right"
@@ -50,7 +51,8 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
               </TouchableOpacity>
               <TouchableOpacity
                 style={styles.btn}
-                onPress={() => doSelect(STAGING_SERVICE)}>
+                onPress={() => doSelect(STAGING_SERVICE)}
+                accessibilityRole="button">
                 <Text style={styles.btnText}>Staging</Text>
                 <FontAwesomeIcon
                   icon="arrow-right"
@@ -61,7 +63,10 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
           ) : undefined}
           <TouchableOpacity
             style={styles.btn}
-            onPress={() => doSelect(PROD_SERVICE)}>
+            onPress={() => doSelect(PROD_SERVICE)}
+            accessibilityRole="button"
+            accessibilityLabel="Select Bluesky Social"
+            accessibilityHint="Sets Bluesky Social as your service provider">
             <Text style={styles.btnText}>Bluesky.Social</Text>
             <FontAwesomeIcon
               icon="arrow-right"
@@ -83,11 +88,23 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
               keyboardAppearance={theme.colorScheme}
               value={customUrl}
               onChangeText={setCustomUrl}
+              accessibilityLabel="Custom domain"
+              // TODO: Simplify this wording further to be understandable by everyone
+              accessibilityHint="Use your domain as your Bluesky client service provider"
             />
             <TouchableOpacity
               testID="customServerSelectBtn"
               style={[pal.borderDark, pal.text, styles.textInputBtn]}
-              onPress={() => doSelect(customUrl)}>
+              onPress={() => doSelect(customUrl)}
+              accessibilityRole="button"
+              accessibilityLabel={`Confirm service. ${
+                customUrl === ''
+                  ? 'Button disabled. Input custom domain to proceed.'
+                  : ''
+              }`}
+              accessibilityHint=""
+              // TODO - accessibility: Need to inform state change on failure
+              disabled={customUrl === ''}>
               <FontAwesomeIcon
                 icon="check"
                 style={[pal.text as FontAwesomeIconStyle, styles.checkIcon]}
diff --git a/src/view/com/modals/Waitlist.tsx b/src/view/com/modals/Waitlist.tsx
index 2795dcffe..7cc78a35f 100644
--- a/src/view/com/modals/Waitlist.tsx
+++ b/src/view/com/modals/Waitlist.tsx
@@ -77,6 +77,9 @@ export function Component({}: {}) {
           keyboardAppearance={theme.colorScheme}
           value={email}
           onChangeText={setEmail}
+          accessible={true}
+          accessibilityLabel="Email"
+          accessibilityHint="Input your email to get on the Bluesky waitlist"
         />
         {error ? (
           <View style={s.mt10}>
@@ -99,7 +102,10 @@ export function Component({}: {}) {
           </View>
         ) : (
           <>
-            <TouchableOpacity onPress={onPressSignup}>
+            <TouchableOpacity
+              onPress={onPressSignup}
+              accessibilityRole="button"
+              accessibilityHint={`Confirms signing up ${email} to the waitlist`}>
               <LinearGradient
                 colors={[gradients.blueLight.start, gradients.blueLight.end]}
                 start={{x: 0, y: 0}}
@@ -110,7 +116,13 @@ export function Component({}: {}) {
                 </Text>
               </LinearGradient>
             </TouchableOpacity>
-            <TouchableOpacity style={[styles.btn, s.mt10]} onPress={onCancel}>
+            <TouchableOpacity
+              style={[styles.btn, s.mt10]}
+              onPress={onCancel}
+              accessibilityRole="button"
+              accessibilityLabel="Cancel waitlist signup"
+              accessibilityHint={`Exits signing up for waitlist with ${email}`}
+              onAccessibilityEscape={onCancel}>
               <Text type="button-lg" style={pal.textLight}>
                 Cancel
               </Text>
diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx
index 8a9b4bf62..c5959cf4c 100644
--- a/src/view/com/modals/crop-image/CropImage.web.tsx
+++ b/src/view/com/modals/crop-image/CropImage.web.tsx
@@ -4,12 +4,13 @@ import ImageEditor from 'react-avatar-editor'
 import {Slider} from '@miblanchard/react-native-slider'
 import LinearGradient from 'react-native-linear-gradient'
 import {Text} from 'view/com/util/text/Text'
-import {Dimensions, Image} from 'lib/media/types'
+import {Dimensions} from 'lib/media/types'
 import {getDataUriSize} from 'lib/media/util'
 import {s, gradients} from 'lib/styles'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 import {SquareIcon, RectWideIcon, RectTallIcon} from 'lib/icons'
+import {Image as RNImage} from 'react-native-image-crop-picker'
 
 enum AspectRatio {
   Square = 'square',
@@ -30,7 +31,7 @@ export function Component({
   onSelect,
 }: {
   uri: string
-  onSelect: (img?: Image) => void
+  onSelect: (img?: RNImage) => void
 }) {
   const store = useStores()
   const pal = usePalette('default')
@@ -92,19 +93,31 @@ export function Component({
           maximumValue={3}
           containerStyle={styles.slider}
         />
-        <TouchableOpacity onPress={doSetAs(AspectRatio.Wide)}>
+        <TouchableOpacity
+          onPress={doSetAs(AspectRatio.Wide)}
+          accessibilityRole="button"
+          accessibilityLabel="Wide"
+          accessibilityHint="Sets image aspect ratio to wide">
           <RectWideIcon
             size={24}
             style={as === AspectRatio.Wide ? s.blue3 : undefined}
           />
         </TouchableOpacity>
-        <TouchableOpacity onPress={doSetAs(AspectRatio.Tall)}>
+        <TouchableOpacity
+          onPress={doSetAs(AspectRatio.Tall)}
+          accessibilityRole="button"
+          accessibilityLabel="Tall"
+          accessibilityHint="Sets image aspect ratio to tall">
           <RectTallIcon
             size={24}
             style={as === AspectRatio.Tall ? s.blue3 : undefined}
           />
         </TouchableOpacity>
-        <TouchableOpacity onPress={doSetAs(AspectRatio.Square)}>
+        <TouchableOpacity
+          onPress={doSetAs(AspectRatio.Square)}
+          accessibilityRole="button"
+          accessibilityLabel="Square"
+          accessibilityHint="Sets image aspect ratio to square">
           <SquareIcon
             size={24}
             style={as === AspectRatio.Square ? s.blue3 : undefined}
@@ -112,13 +125,21 @@ export function Component({
         </TouchableOpacity>
       </View>
       <View style={styles.btns}>
-        <TouchableOpacity onPress={onPressCancel}>
+        <TouchableOpacity
+          onPress={onPressCancel}
+          accessibilityRole="button"
+          accessibilityLabel="Cancel image crop"
+          accessibilityHint="Exits image cropping process">
           <Text type="xl" style={pal.link}>
             Cancel
           </Text>
         </TouchableOpacity>
         <View style={s.flex1} />
-        <TouchableOpacity onPress={onPressDone}>
+        <TouchableOpacity
+          onPress={onPressDone}
+          accessibilityRole="button"
+          accessibilityLabel="Save image crop"
+          accessibilityHint="Saves image crop settings">
           <LinearGradient
             colors={[gradients.blueLight.start, gradients.blueLight.end]}
             start={{x: 0, y: 0}}
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index 8a6578a3c..4ca5fb01a 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -123,7 +123,8 @@ export const FeedItem = observer(function ({
         testID={`feedItem-by-${item.author.handle}`}
         href={itemHref}
         title={itemTitle}
-        noFeedback>
+        noFeedback
+        accessible={false}>
         <Post
           uri={item.uri}
           initView={item.additionalPost}
@@ -163,6 +164,7 @@ export const FeedItem = observer(function ({
   }
 
   return (
+    // eslint-disable-next-line
     <Link
       testID={`feedItem-by-${item.author.handle}`}
       style={[
@@ -178,8 +180,11 @@ export const FeedItem = observer(function ({
       ]}
       href={itemHref}
       title={itemTitle}
-      noFeedback>
+      noFeedback
+      accessible={(item.isLike && authors.length === 1) || item.isRepost}>
       <View style={styles.layoutIcon}>
+        {/* TODO: Prevent conditional rendering and move toward composable
+        notifications for clearer accessibility labeling */}
         {icon === 'HeartIconSolid' ? (
           <HeartIconSolid size={28} style={[styles.icon, ...iconStyle]} />
         ) : (
@@ -192,17 +197,18 @@ export const FeedItem = observer(function ({
       </View>
       <View style={styles.layoutContent}>
         <Pressable
-          onPress={authors.length > 1 ? onToggleAuthorsExpanded : () => {}}>
+          onPress={authors.length > 1 ? onToggleAuthorsExpanded : undefined}
+          accessible={false}>
           <CondensedAuthorsList
             visible={!isAuthorsExpanded}
             authors={authors}
             onToggleAuthorsExpanded={onToggleAuthorsExpanded}
           />
           <ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
-          <View style={styles.meta}>
+          <Text style={styles.meta}>
             <TextLink
               key={authors[0].href}
-              style={[pal.text, s.bold, styles.metaItem]}
+              style={[pal.text, s.bold]}
               href={authors[0].href}
               text={sanitizeDisplayName(
                 authors[0].displayName || authors[0].handle,
@@ -210,17 +216,15 @@ export const FeedItem = observer(function ({
             />
             {authors.length > 1 ? (
               <>
-                <Text style={[styles.metaItem, pal.text]}>and</Text>
-                <Text style={[styles.metaItem, pal.text, s.bold]}>
+                <Text style={[pal.text]}> and </Text>
+                <Text style={[pal.text, s.bold]}>
                   {authors.length - 1} {pluralize(authors.length - 1, 'other')}
                 </Text>
               </>
             ) : undefined}
-            <Text style={[styles.metaItem, pal.text]}>{action}</Text>
-            <Text style={[styles.metaItem, pal.textLight]}>
-              {ago(item.indexedAt)}
-            </Text>
-          </View>
+            <Text style={[pal.text]}> {action}</Text>
+            <Text style={[pal.textLight]}> {ago(item.indexedAt)}</Text>
+          </Text>
         </Pressable>
         {item.isLike || item.isRepost || item.isQuote ? (
           <AdditionalPostText additionalPost={item.additionalPost} />
@@ -245,7 +249,10 @@ function CondensedAuthorsList({
       <View style={styles.avis}>
         <TouchableOpacity
           style={styles.expandedAuthorsCloseBtn}
-          onPress={onToggleAuthorsExpanded}>
+          onPress={onToggleAuthorsExpanded}
+          accessibilityRole="button"
+          accessibilityLabel="Hide user list"
+          accessibilityHint="Collapses list of users for a given notification">
           <FontAwesomeIcon
             icon="angle-up"
             size={18}
@@ -276,27 +283,32 @@ function CondensedAuthorsList({
     )
   }
   return (
-    <View style={styles.avis}>
-      {authors.slice(0, MAX_AUTHORS).map(author => (
-        <View key={author.href} style={s.mr5}>
-          <UserAvatar
-            size={35}
-            avatar={author.avatar}
-            moderation={author.moderation.avatar}
-          />
-        </View>
-      ))}
-      {authors.length > MAX_AUTHORS ? (
-        <Text style={[styles.aviExtraCount, pal.textLight]}>
-          +{authors.length - MAX_AUTHORS}
-        </Text>
-      ) : undefined}
-      <FontAwesomeIcon
-        icon="angle-down"
-        size={18}
-        style={[styles.expandedAuthorsCloseBtnIcon, pal.textLight]}
-      />
-    </View>
+    <TouchableOpacity
+      accessibilityLabel="Show users"
+      accessibilityHint="Opens an expanded list of users in this notification"
+      onPress={onToggleAuthorsExpanded}>
+      <View style={styles.avis}>
+        {authors.slice(0, MAX_AUTHORS).map(author => (
+          <View key={author.href} style={s.mr5}>
+            <UserAvatar
+              size={35}
+              avatar={author.avatar}
+              moderation={author.moderation.avatar}
+            />
+          </View>
+        ))}
+        {authors.length > MAX_AUTHORS ? (
+          <Text style={[styles.aviExtraCount, pal.textLight]}>
+            +{authors.length - MAX_AUTHORS}
+          </Text>
+        ) : undefined}
+        <FontAwesomeIcon
+          icon="angle-down"
+          size={18}
+          style={[styles.expandedAuthorsCloseBtnIcon, pal.textLight]}
+        />
+      </View>
+    </TouchableOpacity>
   )
 }
 
@@ -426,9 +438,6 @@ const styles = StyleSheet.create({
     paddingTop: 6,
     paddingBottom: 2,
   },
-  metaItem: {
-    paddingRight: 3,
-  },
   postText: {
     paddingBottom: 5,
     color: colors.black,
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
index e7d2ec104..725c44603 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -37,7 +37,10 @@ export const FeedsTabBar = observer(
         <TouchableOpacity
           testID="viewHeaderDrawerBtn"
           style={styles.tabBarAvi}
-          onPress={onPressAvi}>
+          onPress={onPressAvi}
+          accessibilityRole="button"
+          accessibilityLabel="Open navigation"
+          accessibilityHint="Access profile and other navigation links">
           <UserAvatar avatar={store.me.avatar} size={30} />
         </TouchableOpacity>
         <TabBar
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index fe1822acb..b886e61e8 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -180,7 +180,11 @@ export const PostThread = observer(function PostThread({
             <Text type="md" style={[pal.text, s.mb10]}>
               The post may have been deleted.
             </Text>
-            <TouchableOpacity onPress={onPressBack}>
+            <TouchableOpacity
+              onPress={onPressBack}
+              accessibilityRole="button"
+              accessibilityLabel="Go back"
+              accessibilityHint="Navigates to the previous screen">
               <Text type="2xl" style={pal.link}>
                 <FontAwesomeIcon
                   icon="angle-left"
@@ -210,7 +214,11 @@ export const PostThread = observer(function PostThread({
           <Text type="md" style={[pal.text, s.mb10]}>
             You have blocked the author or you have been blocked by the author.
           </Text>
-          <TouchableOpacity onPress={onPressBack}>
+          <TouchableOpacity
+            onPress={onPressBack}
+            accessibilityRole="button"
+            accessibilityLabel="Go back"
+            accessibilityHint="Navigates to the previous screen">
             <Text type="2xl" style={pal.link}>
               <FontAwesomeIcon
                 icon="angle-left"
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 8fdcce8ad..191151193 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -151,7 +151,12 @@ export const PostThreadItem = observer(function PostThreadItem({
         moderation={item.moderation.thread}>
         <View style={styles.layout}>
           <View style={styles.layoutAvi}>
-            <Link href={authorHref} title={authorTitle} asAnchor>
+            <Link
+              href={authorHref}
+              title={authorTitle}
+              asAnchor
+              accessibilityLabel={`${item.post.author.handle}'s avatar`}
+              accessibilityHint="">
               <UserAvatar
                 size={52}
                 avatar={item.post.author.avatar}
@@ -183,7 +188,7 @@ export const PostThreadItem = observer(function PostThreadItem({
               <View style={s.flex1} />
               <PostDropdownBtn
                 testID="postDropdownBtn"
-                style={styles.metaItem}
+                style={[styles.metaItem, s.mt2, s.px5]}
                 itemUri={itemUri}
                 itemCid={itemCid}
                 itemHref={itemHref}
@@ -197,7 +202,7 @@ export const PostThreadItem = observer(function PostThreadItem({
                 <FontAwesomeIcon
                   icon="ellipsis-h"
                   size={14}
-                  style={[s.mt2, s.mr5, pal.textLight]}
+                  style={[pal.textLight]}
                 />
               </PostDropdownBtn>
             </View>
@@ -435,10 +440,10 @@ const styles = StyleSheet.create({
     flexDirection: 'row',
   },
   layoutAvi: {
-    width: 70,
     paddingLeft: 10,
     paddingTop: 10,
     paddingBottom: 10,
+    marginRight: 10,
   },
   layoutContent: {
     flex: 1,
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 4accd7aba..d8c4b9d8f 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -282,7 +282,10 @@ const ProfileHeaderLoaded = observer(
               <TouchableOpacity
                 testID="profileHeaderEditProfileButton"
                 onPress={onPressEditProfile}
-                style={[styles.btn, styles.mainBtn, pal.btn]}>
+                style={[styles.btn, styles.mainBtn, pal.btn]}
+                accessibilityRole="button"
+                accessibilityLabel="Edit profile"
+                accessibilityHint="Opens editor for profile display name, avatar, background image, and description">
                 <Text type="button" style={pal.text}>
                   Edit Profile
                 </Text>
@@ -291,7 +294,10 @@ const ProfileHeaderLoaded = observer(
               <TouchableOpacity
                 testID="unblockBtn"
                 onPress={onPressUnblockAccount}
-                style={[styles.btn, styles.mainBtn, pal.btn]}>
+                style={[styles.btn, styles.mainBtn, pal.btn]}
+                accessibilityRole="button"
+                accessibilityLabel="Unblock"
+                accessibilityHint="">
                 <Text type="button" style={[pal.text, s.bold]}>
                   Unblock
                 </Text>
@@ -303,7 +309,10 @@ const ProfileHeaderLoaded = observer(
                   <TouchableOpacity
                     testID="unfollowBtn"
                     onPress={onPressToggleFollow}
-                    style={[styles.btn, styles.mainBtn, pal.btn]}>
+                    style={[styles.btn, styles.mainBtn, pal.btn]}
+                    accessibilityRole="button"
+                    accessibilityLabel={`Unfollow ${view.handle}`}
+                    accessibilityHint={`Hides direct posts from ${view.handle} in your feed`}>
                     <FontAwesomeIcon
                       icon="check"
                       style={[pal.text, s.mr5]}
@@ -317,7 +326,10 @@ const ProfileHeaderLoaded = observer(
                   <TouchableOpacity
                     testID="followBtn"
                     onPress={onPressToggleFollow}
-                    style={[styles.btn, styles.primaryBtn]}>
+                    style={[styles.btn, styles.primaryBtn]}
+                    accessibilityRole="button"
+                    accessibilityLabel={`Follow ${view.handle}`}
+                    accessibilityHint={`Shows direct posts from ${view.handle} in your feed`}>
                     <FontAwesomeIcon
                       icon="plus"
                       style={[s.white as FontAwesomeIconStyle, s.mr5]}
@@ -363,7 +375,10 @@ const ProfileHeaderLoaded = observer(
                 <TouchableOpacity
                   testID="profileHeaderFollowersButton"
                   style={[s.flexRow, s.mr10]}
-                  onPress={onPressFollowers}>
+                  onPress={onPressFollowers}
+                  accessibilityRole="button"
+                  accessibilityLabel={`Show ${view.handle}'s followers`}
+                  accessibilityHint={`Shows folks following ${view.handle}`}>
                   <Text type="md" style={[s.bold, s.mr2, pal.text]}>
                     {formatCount(view.followersCount)}
                   </Text>
@@ -374,7 +389,10 @@ const ProfileHeaderLoaded = observer(
                 <TouchableOpacity
                   testID="profileHeaderFollowsButton"
                   style={[s.flexRow, s.mr10]}
-                  onPress={onPressFollows}>
+                  onPress={onPressFollows}
+                  accessibilityRole="button"
+                  accessibilityLabel={`Show ${view.handle}'s follows`}
+                  accessibilityHint={`Shows folks followed by ${view.handle}`}>
                   <Text type="md" style={[s.bold, s.mr2, pal.text]}>
                     {formatCount(view.followsCount)}
                   </Text>
@@ -382,14 +400,12 @@ const ProfileHeaderLoaded = observer(
                     following
                   </Text>
                 </TouchableOpacity>
-                <View style={[s.flexRow, s.mr10]}>
-                  <Text type="md" style={[s.bold, s.mr2, pal.text]}>
-                    {view.postsCount}
-                  </Text>
+                <Text type="md" style={[s.bold, pal.text]}>
+                  {view.postsCount}{' '}
                   <Text type="md" style={[pal.textLight]}>
                     {pluralize(view.postsCount, 'post')}
                   </Text>
-                </View>
+                </Text>
               </View>
               {view.descriptionRichText ? (
                 <RichText
@@ -440,7 +456,10 @@ const ProfileHeaderLoaded = observer(
         {!isDesktopWeb && !hideBackButton && (
           <TouchableWithoutFeedback
             onPress={onPressBack}
-            hitSlop={BACK_HITSLOP}>
+            hitSlop={BACK_HITSLOP}
+            accessibilityRole="button"
+            accessibilityLabel="Go back"
+            accessibilityHint="Navigates to the previous screen">
             <View style={styles.backBtnWrapper}>
               <BlurView style={styles.backBtn} blurType="dark">
                 <FontAwesomeIcon size={18} icon="angle-left" style={s.white} />
@@ -450,7 +469,10 @@ const ProfileHeaderLoaded = observer(
         )}
         <TouchableWithoutFeedback
           testID="profileHeaderAviButton"
-          onPress={onPressAvi}>
+          onPress={onPressAvi}
+          accessibilityRole="image"
+          accessibilityLabel={`View ${view.handle}'s avatar`}
+          accessibilityHint={`Opens ${view.handle}'s avatar in an image viewer`}>
           <View
             style={[
               pal.view,
diff --git a/src/view/com/search/HeaderWithInput.tsx b/src/view/com/search/HeaderWithInput.tsx
index cc0b90af7..bd92e974a 100644
--- a/src/view/com/search/HeaderWithInput.tsx
+++ b/src/view/com/search/HeaderWithInput.tsx
@@ -54,7 +54,9 @@ export function HeaderWithInput({
         testID="viewHeaderBackOrMenuBtn"
         onPress={onPressMenu}
         hitSlop={MENU_HITSLOP}
-        style={styles.headerMenuBtn}>
+        style={styles.headerMenuBtn}
+        accessibilityLabel="Go back"
+        accessibilityHint="Navigates to the previous screen">
         <UserAvatar size={30} avatar={store.me.avatar} />
       </TouchableOpacity>
       <View
@@ -80,9 +82,15 @@ export function HeaderWithInput({
           onBlur={() => setIsInputFocused(false)}
           onChangeText={onChangeQuery}
           onSubmitEditing={onSubmitQuery}
+          autoFocus={true}
+          accessibilityRole="search"
         />
         {query ? (
-          <TouchableOpacity onPress={onPressClearQuery}>
+          <TouchableOpacity
+            onPress={onPressClearQuery}
+            accessibilityRole="button"
+            accessibilityLabel="Clear search query"
+            accessibilityHint="">
             <FontAwesomeIcon
               icon="xmark"
               size={16}
@@ -93,7 +101,9 @@ export function HeaderWithInput({
       </View>
       {query || isInputFocused ? (
         <View style={styles.headerCancelBtn}>
-          <TouchableOpacity onPress={onPressCancelSearchInner}>
+          <TouchableOpacity
+            onPress={onPressCancelSearchInner}
+            accessibilityRole="button">
             <Text style={pal.text}>Cancel</Text>
           </TouchableOpacity>
         </View>
@@ -110,9 +120,10 @@ const styles = StyleSheet.create({
     paddingVertical: 4,
   },
   headerMenuBtn: {
-    width: 40,
+    width: 30,
     height: 30,
-    marginLeft: 6,
+    borderRadius: 30,
+    marginHorizontal: 6,
   },
   headerSearchContainer: {
     flex: 1,
diff --git a/src/view/com/util/BottomSheetCustomBackdrop.tsx b/src/view/com/util/BottomSheetCustomBackdrop.tsx
index e175b33a5..91379f1c9 100644
--- a/src/view/com/util/BottomSheetCustomBackdrop.tsx
+++ b/src/view/com/util/BottomSheetCustomBackdrop.tsx
@@ -1,5 +1,5 @@
 import React, {useMemo} from 'react'
-import {GestureResponderEvent, TouchableWithoutFeedback} from 'react-native'
+import {TouchableWithoutFeedback} from 'react-native'
 import {BottomSheetBackdropProps} from '@gorhom/bottom-sheet'
 import Animated, {
   Extrapolate,
@@ -8,7 +8,7 @@ import Animated, {
 } from 'react-native-reanimated'
 
 export function createCustomBackdrop(
-  onClose?: ((event: GestureResponderEvent) => void) | undefined,
+  onClose?: (() => void) | undefined,
 ): React.FC<BottomSheetBackdropProps> {
   const CustomBackdrop = ({animatedIndex, style}: BottomSheetBackdropProps) => {
     // animated variables
@@ -27,7 +27,15 @@ export function createCustomBackdrop(
     )
 
     return (
-      <TouchableWithoutFeedback onPress={onClose}>
+      <TouchableWithoutFeedback
+        onPress={onClose}
+        accessibilityLabel="Close bottom drawer"
+        accessibilityHint=""
+        onAccessibilityEscape={() => {
+          if (onClose !== undefined) {
+            onClose()
+          }
+        }}>
         <Animated.View style={containerStyle} />
       </TouchableWithoutFeedback>
     )
diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx
index 5110acf48..503e22084 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, {ComponentProps} from 'react'
 import {observer} from 'mobx-react-lite'
 import {
   Linking,
@@ -29,6 +29,16 @@ type Event =
   | React.MouseEvent<HTMLAnchorElement, MouseEvent>
   | GestureResponderEvent
 
+interface Props extends ComponentProps<typeof TouchableOpacity> {
+  testID?: string
+  style?: StyleProp<ViewStyle>
+  href?: string
+  title?: string
+  children?: React.ReactNode
+  noFeedback?: boolean
+  asAnchor?: boolean
+}
+
 export const Link = observer(function Link({
   testID,
   style,
@@ -37,15 +47,9 @@ export const Link = observer(function Link({
   children,
   noFeedback,
   asAnchor,
-}: {
-  testID?: string
-  style?: StyleProp<ViewStyle>
-  href?: string
-  title?: string
-  children?: React.ReactNode
-  noFeedback?: boolean
-  asAnchor?: boolean
-}) {
+  accessible,
+  ...props
+}: Props) {
   const store = useStores()
   const navigation = useNavigation<NavigationProp>()
 
@@ -64,7 +68,10 @@ export const Link = observer(function Link({
         testID={testID}
         onPress={onPress}
         // @ts-ignore web only -prf
-        href={asAnchor ? sanitizeUrl(href) : undefined}>
+        href={asAnchor ? sanitizeUrl(href) : undefined}
+        accessible={accessible}
+        accessibilityRole="link"
+        {...props}>
         <View style={style}>
           {children ? children : <Text>{title || 'link'}</Text>}
         </View>
@@ -76,8 +83,11 @@ export const Link = observer(function Link({
       testID={testID}
       style={style}
       onPress={onPress}
+      accessible={accessible}
+      accessibilityRole="link"
       // @ts-ignore web only -prf
-      href={asAnchor ? sanitizeUrl(href) : undefined}>
+      href={asAnchor ? sanitizeUrl(href) : undefined}
+      {...props}>
       {children ? children : <Text>{title || 'link'}</Text>}
     </TouchableOpacity>
   )
diff --git a/src/view/com/util/Picker.tsx b/src/view/com/util/Picker.tsx
deleted file mode 100644
index 9007cb1f0..000000000
--- a/src/view/com/util/Picker.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-// TODO: replaceme with something in the design system
-
-import React, {useRef} from 'react'
-import {
-  StyleProp,
-  StyleSheet,
-  TextStyle,
-  TouchableOpacity,
-  TouchableWithoutFeedback,
-  View,
-  ViewStyle,
-} from 'react-native'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import RootSiblings from 'react-native-root-siblings'
-import {Text} from './text/Text'
-import {colors} from 'lib/styles'
-
-interface PickerItem {
-  value: string
-  label: string
-}
-
-interface PickerOpts {
-  style?: StyleProp<ViewStyle>
-  labelStyle?: StyleProp<TextStyle>
-  iconStyle?: FontAwesomeIconStyle
-  items: PickerItem[]
-  value: string
-  onChange: (value: string) => void
-  enabled?: boolean
-}
-
-const MENU_WIDTH = 200
-
-export function Picker({
-  style,
-  labelStyle,
-  iconStyle,
-  items,
-  value,
-  onChange,
-  enabled,
-}: PickerOpts) {
-  const ref = useRef<View>(null)
-  const valueLabel = items.find(item => item.value === value)?.label || value
-  const onPress = () => {
-    if (!enabled) {
-      return
-    }
-    ref.current?.measure(
-      (
-        _x: number,
-        _y: number,
-        width: number,
-        height: number,
-        pageX: number,
-        pageY: number,
-      ) => {
-        createDropdownMenu(pageX, pageY + height, MENU_WIDTH, items, onChange)
-      },
-    )
-  }
-  return (
-    <TouchableWithoutFeedback onPress={onPress}>
-      <View style={[styles.outer, style]} ref={ref}>
-        <View style={styles.label}>
-          <Text style={labelStyle}>{valueLabel}</Text>
-        </View>
-        <FontAwesomeIcon icon="angle-down" style={[styles.icon, iconStyle]} />
-      </View>
-    </TouchableWithoutFeedback>
-  )
-}
-
-function createDropdownMenu(
-  x: number,
-  y: number,
-  width: number,
-  items: PickerItem[],
-  onChange: (value: string) => void,
-): RootSiblings {
-  const onPressItem = (index: number) => {
-    sibling.destroy()
-    onChange(items[index].value)
-  }
-  const onOuterPress = () => sibling.destroy()
-  const sibling = new RootSiblings(
-    (
-      <>
-        <TouchableWithoutFeedback onPress={onOuterPress}>
-          <View style={styles.bg} />
-        </TouchableWithoutFeedback>
-        <View style={[styles.menu, {left: x, top: y, width}]}>
-          {items.map((item, index) => (
-            <TouchableOpacity
-              key={index}
-              style={[styles.menuItem, index !== 0 && styles.menuItemBorder]}
-              onPress={() => onPressItem(index)}>
-              <Text style={styles.menuItemLabel}>{item.label}</Text>
-            </TouchableOpacity>
-          ))}
-        </View>
-      </>
-    ),
-  )
-  return sibling
-}
-
-const styles = StyleSheet.create({
-  outer: {
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  label: {
-    marginRight: 5,
-  },
-  icon: {},
-  bg: {
-    position: 'absolute',
-    top: 0,
-    right: 0,
-    bottom: 0,
-    left: 0,
-    backgroundColor: '#000',
-    opacity: 0.1,
-  },
-  menu: {
-    position: 'absolute',
-    backgroundColor: '#fff',
-    borderRadius: 14,
-    opacity: 1,
-    paddingVertical: 6,
-  },
-  menuItem: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingVertical: 6,
-    paddingLeft: 15,
-    paddingRight: 30,
-  },
-  menuItemBorder: {
-    borderTopWidth: 1,
-    borderTopColor: colors.gray2,
-    marginTop: 4,
-    paddingTop: 12,
-  },
-  menuItemIcon: {
-    marginLeft: 6,
-    marginRight: 8,
-  },
-  menuItemLabel: {
-    fontSize: 15,
-  },
-})
diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx
index 07a67fd8a..725f3bbbe 100644
--- a/src/view/com/util/PostCtrls.tsx
+++ b/src/view/com/util/PostCtrls.tsx
@@ -170,83 +170,94 @@ export function PostCtrls(opts: PostCtrlsOpts) {
 
   return (
     <View style={[styles.ctrls, opts.style]}>
-      <View>
-        <TouchableOpacity
-          testID="replyBtn"
-          style={styles.ctrl}
-          hitSlop={HITSLOP}
-          onPress={opts.onPressReply}>
-          <CommentBottomArrow
-            style={[defaultCtrlColor, opts.big ? s.mt2 : styles.mt1]}
-            strokeWidth={3}
-            size={opts.big ? 20 : 15}
-          />
-          {typeof opts.replyCount !== 'undefined' ? (
-            <Text style={[defaultCtrlColor, s.ml5, s.f15]}>
-              {opts.replyCount}
-            </Text>
-          ) : undefined}
-        </TouchableOpacity>
-      </View>
-      <View>
-        <TouchableOpacity
-          testID="repostBtn"
-          hitSlop={HITSLOP}
-          onPress={onPressToggleRepostWrapper}
-          style={styles.ctrl}>
-          <RepostIcon
+      <TouchableOpacity
+        testID="replyBtn"
+        style={styles.ctrl}
+        hitSlop={HITSLOP}
+        onPress={opts.onPressReply}
+        accessibilityRole="button"
+        accessibilityLabel="Reply"
+        accessibilityHint="Opens reply composer">
+        <CommentBottomArrow
+          style={[defaultCtrlColor, opts.big ? s.mt2 : styles.mt1]}
+          strokeWidth={3}
+          size={opts.big ? 20 : 15}
+        />
+        {typeof opts.replyCount !== 'undefined' ? (
+          <Text style={[defaultCtrlColor, s.ml5, s.f15]}>
+            {opts.replyCount}
+          </Text>
+        ) : undefined}
+      </TouchableOpacity>
+      <TouchableOpacity
+        testID="repostBtn"
+        hitSlop={HITSLOP}
+        onPress={onPressToggleRepostWrapper}
+        style={styles.ctrl}
+        accessibilityRole="button"
+        accessibilityLabel={opts.isReposted ? 'Undo repost' : 'Repost'}
+        accessibilityHint={
+          opts.isReposted
+            ? `Remove your repost of ${opts.author}'s post`
+            : `Repost or quote post ${opts.author}'s post`
+        }>
+        <RepostIcon
+          style={
+            opts.isReposted
+              ? (styles.ctrlIconReposted as StyleProp<ViewStyle>)
+              : defaultCtrlColor
+          }
+          strokeWidth={2.4}
+          size={opts.big ? 24 : 20}
+        />
+        {typeof opts.repostCount !== 'undefined' ? (
+          <Text
+            testID="repostCount"
             style={
               opts.isReposted
-                ? (styles.ctrlIconReposted as StyleProp<ViewStyle>)
-                : defaultCtrlColor
-            }
-            strokeWidth={2.4}
-            size={opts.big ? 24 : 20}
+                ? [s.bold, s.green3, s.f15, s.ml5]
+                : [defaultCtrlColor, s.f15, s.ml5]
+            }>
+            {opts.repostCount}
+          </Text>
+        ) : undefined}
+      </TouchableOpacity>
+      <TouchableOpacity
+        testID="likeBtn"
+        style={styles.ctrl}
+        hitSlop={HITSLOP}
+        onPress={onPressToggleLikeWrapper}
+        accessibilityRole="button"
+        accessibilityLabel={opts.isLiked ? 'Unlike' : 'Like'}
+        accessibilityHint={
+          opts.isReposted
+            ? `Removes like from ${opts.author}'s post`
+            : `Like ${opts.author}'s post`
+        }>
+        {opts.isLiked ? (
+          <HeartIconSolid
+            style={styles.ctrlIconLiked as StyleProp<ViewStyle>}
+            size={opts.big ? 22 : 16}
           />
-          {typeof opts.repostCount !== 'undefined' ? (
-            <Text
-              testID="repostCount"
-              style={
-                opts.isReposted
-                  ? [s.bold, s.green3, s.f15, s.ml5]
-                  : [defaultCtrlColor, s.f15, s.ml5]
-              }>
-              {opts.repostCount}
-            </Text>
-          ) : undefined}
-        </TouchableOpacity>
-      </View>
-      <View>
-        <TouchableOpacity
-          testID="likeBtn"
-          style={styles.ctrl}
-          hitSlop={HITSLOP}
-          onPress={onPressToggleLikeWrapper}>
-          {opts.isLiked ? (
-            <HeartIconSolid
-              style={styles.ctrlIconLiked as StyleProp<ViewStyle>}
-              size={opts.big ? 22 : 16}
-            />
-          ) : (
-            <HeartIcon
-              style={[defaultCtrlColor, opts.big ? styles.mt1 : undefined]}
-              strokeWidth={3}
-              size={opts.big ? 20 : 16}
-            />
-          )}
-          {typeof opts.likeCount !== 'undefined' ? (
-            <Text
-              testID="likeCount"
-              style={
-                opts.isLiked
-                  ? [s.bold, s.red3, s.f15, s.ml5]
-                  : [defaultCtrlColor, s.f15, s.ml5]
-              }>
-              {opts.likeCount}
-            </Text>
-          ) : undefined}
-        </TouchableOpacity>
-      </View>
+        ) : (
+          <HeartIcon
+            style={[defaultCtrlColor, opts.big ? styles.mt1 : undefined]}
+            strokeWidth={3}
+            size={opts.big ? 20 : 16}
+          />
+        )}
+        {typeof opts.likeCount !== 'undefined' ? (
+          <Text
+            testID="likeCount"
+            style={
+              opts.isLiked
+                ? [s.bold, s.red3, s.f15, s.ml5]
+                : [defaultCtrlColor, s.f15, s.ml5]
+            }>
+            {opts.likeCount}
+          </Text>
+        ) : undefined}
+      </TouchableOpacity>
       <View>
         {opts.big ? undefined : (
           <PostDropdownBtn
diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx
index 016ea77b8..223a069c8 100644
--- a/src/view/com/util/Selector.tsx
+++ b/src/view/com/util/Selector.tsx
@@ -85,6 +85,8 @@ export function Selector({
     onSelect?.(index)
   }
 
+  const numItems = items.length
+
   return (
     <View
       style={[pal.view, styles.outer]}
@@ -97,7 +99,9 @@ export function Selector({
           <Pressable
             testID={`selector-${i}`}
             key={item}
-            onPress={() => onPressItem(i)}>
+            onPress={() => onPressItem(i)}
+            accessibilityLabel={`Select ${item}`}
+            accessibilityHint={`Select option ${i} of ${numItems}`}>
             <View style={styles.item} ref={itemRefs[i]}>
               <Text
                 style={
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 7f55bf773..a2e607e47 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -150,6 +150,7 @@ export function UserAvatar({
             borderRadius: Math.floor(size / 2),
           }}
           source={{uri: avatar}}
+          accessibilityRole="image"
         />
       ) : (
         <DefaultAvatar size={size} />
@@ -167,7 +168,11 @@ export function UserAvatar({
     <View style={{width: size, height: size}}>
       <HighPriorityImage
         testID="userAvatarImage"
-        style={{width: size, height: size, borderRadius: Math.floor(size / 2)}}
+        style={{
+          width: size,
+          height: size,
+          borderRadius: Math.floor(size / 2),
+        }}
         contentFit="cover"
         source={{uri: avatar}}
         blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index 14459bf77..51cfbccbb 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -5,7 +5,6 @@ import {IconProp} from '@fortawesome/fontawesome-svg-core'
 import {Image} from 'expo-image'
 import {colors} from 'lib/styles'
 import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
-import {Image as TImage} from 'lib/media/types'
 import {useStores} from 'state/index'
 import {
   usePhotoLibraryPermission,
@@ -15,6 +14,7 @@ import {DropdownButton} from './forms/DropdownButton'
 import {usePalette} from 'lib/hooks/usePalette'
 import {AvatarModeration} from 'lib/labeling/types'
 import {isWeb, isAndroid} from 'platform/detection'
+import {Image as RNImage} from 'react-native-image-crop-picker'
 
 export function UserBanner({
   banner,
@@ -23,7 +23,7 @@ export function UserBanner({
 }: {
   banner?: string | null
   moderation?: AvatarModeration
-  onSelectNewBanner?: (img: TImage | null) => void
+  onSelectNewBanner?: (img: RNImage | null) => void
 }) {
   const store = useStores()
   const pal = usePalette('default')
@@ -94,6 +94,8 @@ export function UserBanner({
           testID="userBannerImage"
           style={styles.bannerImage}
           source={{uri: banner}}
+          accessible={true}
+          accessibilityIgnoresInvertColors
         />
       ) : (
         <View
@@ -118,6 +120,8 @@ export function UserBanner({
       resizeMode="cover"
       source={{uri: banner}}
       blurRadius={moderation?.blur ? 100 : 0}
+      accessible={true}
+      accessibilityIgnoresInvertColors
     />
   ) : (
     <View
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index 816c835cc..9c85cfa24 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -60,7 +60,14 @@ export const ViewHeader = observer(function ({
           testID="viewHeaderDrawerBtn"
           onPress={canGoBack ? onPressBack : onPressMenu}
           hitSlop={BACK_HITSLOP}
-          style={canGoBack ? styles.backBtn : styles.backBtnWide}>
+          style={canGoBack ? styles.backBtn : styles.backBtnWide}
+          accessibilityRole="button"
+          accessibilityLabel={canGoBack ? 'Go back' : 'Go to menu'}
+          accessibilityHint={
+            canGoBack
+              ? 'Navigates to the previous screen'
+              : 'Navigates to the menu'
+          }>
           {canGoBack ? (
             <FontAwesomeIcon
               size={18}
@@ -171,9 +178,9 @@ const styles = StyleSheet.create({
     height: 30,
   },
   backBtnWide: {
-    width: 40,
+    width: 30,
     height: 30,
-    marginLeft: 6,
+    paddingHorizontal: 6,
   },
   backIcon: {
     marginTop: 6,
diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx
index 02717053d..f9ef0945d 100644
--- a/src/view/com/util/ViewSelector.tsx
+++ b/src/view/com/util/ViewSelector.tsx
@@ -132,7 +132,12 @@ export function Selector({
           <Pressable
             testID={`selector-${i}`}
             key={item}
-            onPress={() => onPressItem(i)}>
+            onPress={() => onPressItem(i)}
+            accessibilityLabel={item}
+            accessibilityHint={`Selects ${item}`}
+            // TODO: Modify the component API such that lint fails
+            // at the invocation site as well
+          >
             <View
               style={[
                 styles.item,
diff --git a/src/view/com/util/error/ErrorMessage.tsx b/src/view/com/util/error/ErrorMessage.tsx
index cc0df1b59..370f10ae3 100644
--- a/src/view/com/util/error/ErrorMessage.tsx
+++ b/src/view/com/util/error/ErrorMessage.tsx
@@ -47,7 +47,10 @@ export function ErrorMessage({
         <TouchableOpacity
           testID="errorMessageTryAgainButton"
           style={styles.btn}
-          onPress={onPressTryAgain}>
+          onPress={onPressTryAgain}
+          accessibilityRole="button"
+          accessibilityLabel="Retry"
+          accessibilityHint="Retries the last action, which errored out">
           <FontAwesomeIcon
             icon="arrows-rotate"
             style={{color: theme.palette.error.icon}}
diff --git a/src/view/com/util/error/ErrorScreen.tsx b/src/view/com/util/error/ErrorScreen.tsx
index c849e37db..a5deeb18f 100644
--- a/src/view/com/util/error/ErrorScreen.tsx
+++ b/src/view/com/util/error/ErrorScreen.tsx
@@ -57,7 +57,9 @@ export function ErrorScreen({
             testID="errorScreenTryAgainButton"
             type="default"
             style={[styles.btn]}
-            onPress={onPressTryAgain}>
+            onPress={onPressTryAgain}
+            accessibilityLabel="Retry"
+            accessibilityHint="Retries the last action, which errored out">
             <FontAwesomeIcon
               icon="arrows-rotate"
               style={pal.link as FontAwesomeIconStyle}
diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx
index 3d44c0dd4..5eb4a6588 100644
--- a/src/view/com/util/fab/FABInner.tsx
+++ b/src/view/com/util/fab/FABInner.tsx
@@ -1,25 +1,19 @@
-import React from 'react'
+import React, {ComponentProps} from 'react'
 import {observer} from 'mobx-react-lite'
-import {
-  Animated,
-  GestureResponderEvent,
-  StyleSheet,
-  TouchableWithoutFeedback,
-} from 'react-native'
+import {Animated, StyleSheet, TouchableWithoutFeedback} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {gradients} from 'lib/styles'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {useStores} from 'state/index'
 import {isMobileWeb} from 'platform/detection'
 
-type OnPress = ((event: GestureResponderEvent) => void) | undefined
-export interface FABProps {
+export interface FABProps
+  extends ComponentProps<typeof TouchableWithoutFeedback> {
   testID?: string
   icon: JSX.Element
-  onPress: OnPress
 }
 
-export const FABInner = observer(({testID, icon, onPress}: FABProps) => {
+export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
   const store = useStores()
   const interp = useAnimatedValue(0)
   React.useEffect(() => {
@@ -34,7 +28,7 @@ export const FABInner = observer(({testID, icon, onPress}: FABProps) => {
     transform: [{translateY: Animated.multiply(interp, 60)}],
   }
   return (
-    <TouchableWithoutFeedback testID={testID} onPress={onPress}>
+    <TouchableWithoutFeedback testID={testID} {...props}>
       <Animated.View
         style={[styles.outer, isMobileWeb && styles.mobileWebOuter, transform]}>
         <LinearGradient
diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx
index 8548860d0..3b5b00284 100644
--- a/src/view/com/util/forms/Button.tsx
+++ b/src/view/com/util/forms/Button.tsx
@@ -26,6 +26,7 @@ export type ButtonType =
   | 'secondary-light'
   | 'default-light'
 
+// TODO: Enforce that button always has a label
 export function Button({
   type = 'primary',
   label,
@@ -131,7 +132,8 @@ export function Button({
     <Pressable
       style={[typeOuterStyle, styles.outer, style]}
       onPress={onPressWrapped}
-      testID={testID}>
+      testID={testID}
+      accessibilityRole="button">
       {label ? (
         <Text type="button" style={[typeLabelStyle, labelStyle]}>
           {label}
diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx
index 725d45c1b..04346d91f 100644
--- a/src/view/com/util/forms/DropdownButton.tsx
+++ b/src/view/com/util/forms/DropdownButton.tsx
@@ -1,4 +1,4 @@
-import React, {useRef} from 'react'
+import React, {PropsWithChildren, useMemo, useRef} from 'react'
 import {
   Dimensions,
   StyleProp,
@@ -39,6 +39,19 @@ type MaybeDropdownItem = DropdownItem | false | undefined
 
 export type DropdownButtonType = ButtonType | 'bare'
 
+interface DropdownButtonProps {
+  testID?: string
+  type?: DropdownButtonType
+  style?: StyleProp<ViewStyle>
+  items: MaybeDropdownItem[]
+  label?: string
+  menuWidth?: number
+  children?: React.ReactNode
+  openToRight?: boolean
+  rightOffset?: number
+  bottomOffset?: number
+}
+
 export function DropdownButton({
   testID,
   type = 'bare',
@@ -50,18 +63,7 @@ export function DropdownButton({
   openToRight = false,
   rightOffset = 0,
   bottomOffset = 0,
-}: {
-  testID?: string
-  type?: DropdownButtonType
-  style?: StyleProp<ViewStyle>
-  items: MaybeDropdownItem[]
-  label?: string
-  menuWidth?: number
-  children?: React.ReactNode
-  openToRight?: boolean
-  rightOffset?: number
-  bottomOffset?: number
-}) {
+}: PropsWithChildren<DropdownButtonProps>) {
   const ref1 = useRef<TouchableOpacity>(null)
   const ref2 = useRef<View>(null)
 
@@ -105,6 +107,18 @@ export function DropdownButton({
     )
   }
 
+  const numItems = useMemo(
+    () =>
+      items.filter(item => {
+        if (item === undefined || item === false) {
+          return false
+        }
+
+        return isBtn(item)
+      }).length,
+    [items],
+  )
+
   if (type === 'bare') {
     return (
       <TouchableOpacity
@@ -112,7 +126,10 @@ export function DropdownButton({
         style={style}
         onPress={onPress}
         hitSlop={HITSLOP}
-        ref={ref1}>
+        ref={ref1}
+        accessibilityRole="button"
+        accessibilityLabel={`Opens ${numItems} options`}
+        accessibilityHint={`Opens ${numItems} options`}>
         {children}
       </TouchableOpacity>
     )
@@ -283,9 +300,20 @@ const DropdownItems = ({
   const separatorColor =
     theme.colorScheme === 'dark' ? pal.borderDark : pal.border
 
+  const numItems = items.filter(isBtn).length
+
   return (
     <>
-      <TouchableWithoutFeedback onPress={onOuterPress}>
+      <TouchableWithoutFeedback
+        onPress={onOuterPress}
+        // TODO: Refactor dropdown components to:
+        // - (On web, if not handled by React Native) use semantic <select />
+        // and <option /> elements for keyboard navigation out of the box
+        // - (On mobile) be buttons by default, accept `label` and `nativeID`
+        // props, and always have an explicit label
+        accessibilityRole="button"
+        accessibilityLabel="Toggle dropdown"
+        accessibilityHint="">
         <View style={[styles.bg]} />
       </TouchableWithoutFeedback>
       <View
@@ -301,7 +329,9 @@ const DropdownItems = ({
                 testID={item.testID}
                 key={index}
                 style={[styles.menuItem]}
-                onPress={() => onPressItem(index)}>
+                onPress={() => onPressItem(index)}
+                accessibilityLabel={item.label}
+                accessibilityHint={`Option ${index + 1} of ${numItems}`}>
                 {item.icon && (
                   <FontAwesomeIcon
                     style={styles.icon}
diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx
index 8c31f5614..e6aba46f3 100644
--- a/src/view/com/util/images/AutoSizedImage.tsx
+++ b/src/view/com/util/images/AutoSizedImage.tsx
@@ -62,12 +62,17 @@ export function AutoSizedImage({
         onLongPress={onLongPress}
         onPressIn={onPressIn}
         delayPressIn={DELAY_PRESS_IN}
-        style={[styles.container, style]}>
+        style={[styles.container, style]}
+        accessible={true}
+        accessibilityLabel="Share image"
+        accessibilityHint="Opens ways of sharing image">
         <Image
           style={[styles.image, {aspectRatio}]}
           source={uri}
           accessible={true} // Must set for `accessibilityLabel` to work
+          accessibilityIgnoresInvertColors
           accessibilityLabel={alt}
+          accessibilityHint=""
         />
         {children}
       </TouchableOpacity>
@@ -80,7 +85,9 @@ export function AutoSizedImage({
         style={[styles.image, {aspectRatio}]}
         source={{uri}}
         accessible={true} // Must set for `accessibilityLabel` to work
+        accessibilityIgnoresInvertColors
         accessibilityLabel={alt}
+        accessibilityHint=""
       />
       {children}
     </View>
diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx
index 78ced0668..5b6c3384d 100644
--- a/src/view/com/util/images/Gallery.tsx
+++ b/src/view/com/util/images/Gallery.tsx
@@ -41,16 +41,25 @@ export const GalleryItem: FC<GalleryItemProps> = ({
         delayPressIn={DELAY_PRESS_IN}
         onPress={() => onPress?.(index)}
         onPressIn={() => onPressIn?.(index)}
-        onLongPress={() => onLongPress?.(index)}>
+        onLongPress={() => onLongPress?.(index)}
+        accessibilityRole="button"
+        accessibilityLabel="View image"
+        accessibilityHint="">
         <Image
           source={{uri: image.thumb}}
           style={imageStyle}
           accessible={true}
           accessibilityLabel={image.alt}
+          accessibilityHint=""
+          accessibilityIgnoresInvertColors
         />
       </TouchableOpacity>
       {image.alt === '' ? null : (
-        <Pressable onPress={onPressAltText}>
+        <Pressable
+          onPress={onPressAltText}
+          accessibilityRole="button"
+          accessibilityLabel="View alt text"
+          accessibilityHint="Opens modal with alt text">
           <Text style={styles.alt}>ALT</Text>
         </Pressable>
       )}
diff --git a/src/view/com/util/images/Image.tsx b/src/view/com/util/images/Image.tsx
index e3d0d7fcc..e779fa378 100644
--- a/src/view/com/util/images/Image.tsx
+++ b/src/view/com/util/images/Image.tsx
@@ -8,5 +8,7 @@ export function HighPriorityImage({source, ...props}: HighPriorityImageProps) {
   const updatedSource = {
     uri: typeof source === 'object' && source ? source.uri : '',
   } satisfies ImageSource
-  return <Image source={updatedSource} {...props} />
+  return (
+    <Image accessibilityIgnoresInvertColors source={updatedSource} {...props} />
+  )
 }
diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx
index 5c232e0b4..88494bba3 100644
--- a/src/view/com/util/images/ImageHorzList.tsx
+++ b/src/view/com/util/images/ImageHorzList.tsx
@@ -16,15 +16,33 @@ interface Props {
 }
 
 export function ImageHorzList({images, onPress, style}: Props) {
+  const numImages = images.length
   return (
     <View style={[styles.flexRow, style]}>
       {images.map(({thumb, alt}, i) => (
-        <TouchableWithoutFeedback key={i} onPress={() => onPress?.(i)}>
+        <TouchableWithoutFeedback
+          key={i}
+          onPress={() => onPress?.(i)}
+          accessible={true}
+          accessibilityLabel={`Open image ${i} of ${numImages}`}
+          accessibilityHint="Opens image in viewer"
+          accessibilityActions={[{name: 'press', label: 'Press'}]}
+          onAccessibilityAction={action => {
+            switch (action.nativeEvent.actionName) {
+              case 'press':
+                onPress?.(0)
+                break
+              default:
+                break
+            }
+          }}>
           <Image
             source={{uri: thumb}}
             style={styles.image}
             accessible={true}
-            accessibilityLabel={alt}
+            accessibilityIgnoresInvertColors
+            accessibilityHint={alt}
+            accessibilityLabel=""
           />
         </TouchableWithoutFeedback>
       ))}
diff --git a/src/view/com/util/load-latest/LoadLatestBtn.web.tsx b/src/view/com/util/load-latest/LoadLatestBtn.web.tsx
index 1b6f18b62..839685029 100644
--- a/src/view/com/util/load-latest/LoadLatestBtn.web.tsx
+++ b/src/view/com/util/load-latest/LoadLatestBtn.web.tsx
@@ -23,7 +23,10 @@ export const LoadLatestBtn = ({
     <TouchableOpacity
       style={[pal.view, pal.borderDark, styles.loadLatest]}
       onPress={onPress}
-      hitSlop={HITSLOP}>
+      hitSlop={HITSLOP}
+      accessibilityRole="button"
+      accessibilityLabel={`Load new ${label}`}
+      accessibilityHint="">
       <Text type="md-bold" style={pal.text}>
         <UpIcon size={16} strokeWidth={1} style={[pal.text, styles.icon]} />
         Load new {label}
diff --git a/src/view/com/util/load-latest/LoadLatestBtnMobile.tsx b/src/view/com/util/load-latest/LoadLatestBtnMobile.tsx
index 75a812760..5279696a2 100644
--- a/src/view/com/util/load-latest/LoadLatestBtnMobile.tsx
+++ b/src/view/com/util/load-latest/LoadLatestBtnMobile.tsx
@@ -23,7 +23,10 @@ export const LoadLatestBtn = observer(
           },
         ]}
         onPress={onPress}
-        hitSlop={HITSLOP}>
+        hitSlop={HITSLOP}
+        accessibilityRole="button"
+        accessibilityLabel={`Load new ${label}`}
+        accessibilityHint={`Loads new ${label}`}>
         <LinearGradient
           colors={[gradients.blueLight.start, gradients.blueLight.end]}
           start={{x: 0, y: 0}}
diff --git a/src/view/com/util/moderation/ContentHider.tsx b/src/view/com/util/moderation/ContentHider.tsx
index 74fb479ad..0f3e47d61 100644
--- a/src/view/com/util/moderation/ContentHider.tsx
+++ b/src/view/com/util/moderation/ContentHider.tsx
@@ -55,7 +55,14 @@ export function ContentHider({
         </Text>
         <TouchableOpacity
           style={styles.showBtn}
-          onPress={() => setOverride(v => !v)}>
+          onPress={() => setOverride(v => !v)}
+          accessibilityLabel={override ? 'Hide post' : 'Show post'}
+          // TODO: The text labelling should be split up so controls have unique roles
+          accessibilityHint={
+            override
+              ? 'Re-hide post'
+              : 'Shows post hidden based on your moderation settings'
+          }>
           <Text type="md" style={pal.link}>
             {override ? 'Hide' : 'Show'}
           </Text>
diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/view/com/util/moderation/PostHider.tsx
index b3c4c9593..2cc7ea62b 100644
--- a/src/view/com/util/moderation/PostHider.tsx
+++ b/src/view/com/util/moderation/PostHider.tsx
@@ -46,7 +46,8 @@ export function PostHider({
           </Text>
           <TouchableOpacity
             style={styles.showBtn}
-            onPress={() => setOverride(v => !v)}>
+            onPress={() => setOverride(v => !v)}
+            accessibilityRole="button">
             <Text type="md" style={pal.link}>
               {override ? 'Hide' : 'Show'} post
             </Text>
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index 6a7759840..929c85adc 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -136,7 +136,10 @@ export function PostEmbeds({
                 <Pressable
                   onPress={() => {
                     onPressAltText(alt)
-                  }}>
+                  }}
+                  accessibilityRole="button"
+                  accessibilityLabel="View alt text"
+                  accessibilityHint="Opens modal with alt text">
                   <Text style={styles.alt}>ALT</Text>
                 </Pressable>
               )}