Skip to content

feat(a11y): label post action-row + overflow-menu controls for screen readers#3288

Merged
feruzm merged 2 commits into
developmentfrom
fix/action-row-accessibility
Jun 23, 2026
Merged

feat(a11y): label post action-row + overflow-menu controls for screen readers#3288
feruzm merged 2 commits into
developmentfrom
fix/action-row-accessibility

Conversation

@feruzm

@feruzm feruzm commented Jun 23, 2026

Copy link
Copy Markdown
Member

What

Follows up the upvote a11y fix (#3286) with a broad pass over the rest of the post action surface so screen-reader users (VoiceOver / TalkBack) can find, identify, and operate every interactive control — not just the upvote arrow.

How

Central, additive change to two shared components — both rendered a touchable with no accessibility metadata:

  • TextWithIcon (votes / reblog / comment / tip / stats / replies-toggle) and IconButton (tip / edit / delete / overflow menu) now forward optional accessibilityLabel / accessibilityHint, expose accessibilityRole="button" only when interactive (gated on a press handler, mirroring each component's existing disabled logic), and set accessibilityState. Props are optional → every existing call site is unaffected.

Labels added at the call sites (count-aware, via intl.formatMessage(..., { count })):

Surface Controls labeled
Feed card — postCardActionsPanel votes, reblog, comment, tip
Single post bar — postDisplayView votes, reblog, comment, tip, view-stats
Comment & wave row — commentView votes, reply, tip, edit, delete, show/hide replies
Overflow "⋯" menus post options on feed cards (postCardHeader) + on comments/waves (postHeaderDescription)

UpvoteButton was already done in #3286. Waves are covered transitively (the waves screen renders through CommentView), and all feed/profile/community/search/bookmark lists route through PostCardActionsPanel — so no duplicate surfaces.

16 new post.a11y_* strings in en-US.json. Every formatMessage carries a defaultMessage, so the ~37 non-English locales fall back to readable English instead of raw IDs (the lesson from #3286 review).

Validation

  • Prettier 2.8.8 (CI-pinned) ✓ · ESLint ✓ (0 errors; pre-existing warnings only) · valid JSON
  • A 3-lens adversarial audit (completeness / shared-component safety / correctness) drove the final scope: it confirmed the bottom bars were complete, flagged the two overflow-menu buttons as the material gaps (now fixed), and verified all i18n keys resolve, intl is in scope (incl. the useMemo dep in postDisplayView), and counts match what each control displays.
  • Manual: please verify on device — focusing each control should announce e.g. "5 votes, button" / "Post options, button" / "Send tip, button".

Deliberately deferred

  • Comment "Read more" already announces via its child text + now carries button role (functionally accessible); reblog-by indicator has an implicit text label. Both are low-priority and left as-is.

Summary by CodeRabbit

  • Accessibility Improvements
    • Added screen reader labels and hints to interactive elements including vote buttons, comment actions, post options, and tip controls.
    • Components now properly announce counts and available actions to assistive devices.
    • Enhanced button accessibility metadata to ensure proper detection across the app.

… readers

Extends the upvote a11y work (#3286) across the whole post action surface so
VoiceOver/TalkBack announce every interactive control with a meaningful,
count-aware name and role.

Central (optional, additive props): TextWithIcon and IconButton now forward
accessibilityLabel/accessibilityHint and expose accessibilityRole="button"
when interactive (gated on a press handler), plus accessibilityState.

Call sites labeled:
- Feed card (postCardActionsPanel): votes / reblog / comment / tip
- Single post bar (postDisplayView): votes / reblog / comment / tip / view-stats
- Comment & wave row (commentView): votes / reply / tip / edit / delete /
  show-hide replies
- Overflow menus: post options on feed cards (postCardHeader) and on
  comments/waves (postHeaderDescription)

Counts are interpolated ({count} votes/reblogs/comments/tips/views) and every
formatMessage carries a defaultMessage so non-English locales fall back to
English instead of raw message IDs. 16 new post.a11y_* strings (en-US).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7c7b2b084

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread node_modules Outdated
@@ -0,0 +1 @@
/root/ecency-mobile/node_modules No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the tracked node_modules symlink

This commit tracks node_modules as a symlink to /root/ecency-mobile/node_modules. In any fresh checkout that is not running with that exact machine-local path, the dependency directory is dangling or points outside the repo, so install/test scripts that dereference node_modules/... can fail or write under /root instead of the workspace. Since node_modules/ is already ignored, this should stay untracked.

Useful? React with 👍 / 👎.

accessibilityRole={onPress ? 'button' : undefined}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
accessibilityState={{ disabled: !!disabled }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark loading icon buttons as disabled for a11y

For callers that pass isLoading without also setting disabled (for example the edit-avatar/profile-cover and snippet delete buttons), onPress is suppressed while loading but this new accessibility state still reports the control as enabled. Screen-reader users can focus an enabled button that does nothing while the spinner is shown; include isLoading in the disabled state or disable the touchable consistently.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b124f128-500a-4682-8deb-33fb193d7090

📥 Commits

Reviewing files that changed from the base of the PR and between e7c7b2b and 9751612.

📒 Files selected for processing (6)
  • src/components/basicUIElements/view/textWithIcon/textWithIconView.tsx
  • src/components/comment/view/commentView.tsx
  • src/components/iconButton/view/iconButtonView.tsx
  • src/components/postCard/children/postCardActionsPanel.tsx
  • src/components/postView/view/postDisplayView.tsx
  • src/config/locales/en-US.json
✅ Files skipped from review due to trivial changes (1)
  • src/config/locales/en-US.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/iconButton/view/iconButtonView.tsx
  • src/components/comment/view/commentView.tsx
  • src/components/postView/view/postDisplayView.tsx
  • src/components/postCard/children/postCardActionsPanel.tsx

📝 Walkthrough

Walkthrough

Adds React Native accessibility props (accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityState) to IconButton and TextWithIcon primitives, then applies localized strings from 16 new en-US.json keys across post card action panel, post header, comment view action panel, and post display view action panel and stats elements.

Changes

Accessibility Support

Layer / File(s) Summary
Locale strings and primitive component a11y props
src/config/locales/en-US.json, src/components/iconButton/view/iconButtonView.tsx, src/components/basicUIElements/view/textWithIcon/textWithIconView.tsx
Adds 16 a11y_* translation keys. IconButton gains accessibilityLabel/accessibilityHint props and sets accessibilityRole='button' when onPress is present, with accessibilityState.disabled reflecting both disabled and isLoading. TextWithIcon gains the same props plus an internal _interactive flag that gates accessibilityRole and accessibilityState.
Post card action panel and header a11y
src/components/postCard/children/postCardActionsPanel.tsx, src/components/postCard/children/postCardHeader.tsx, src/components/postElements/headerDescription/view/postHeaderDescription.tsx
postCardActionsPanel initializes useIntl and adds localized, pluralized accessibilityLabel/accessibilityHint to voters, reblogs, comments, and tip controls. postCardHeader and postHeaderDescription each add a localized accessibilityLabel to their options IconButton.
Comment view action panel a11y
src/components/comment/view/commentView.tsx
Adds localized accessibilityLabel and contextual accessibilityHint to votes (conditional hint when nonzero), comments/replies, tip, edit, and delete controls. The replies chevron toggle receives a repliesToggle-aware label ("Show replies"/"Hide replies"). The votes isClickable is also now gated on _totalVotes > 0.
Post display view sticky panel and header stats a11y
src/components/postView/view/postDisplayView.tsx
Wires localized, pluralized accessibilityLabel and accessibilityHint to votes, reblogs, logged-in comments, logged-out comments (label only), and tips TextWithIcon elements in the sticky action panel, and to the header views stat. Adds intl to the _renderActionPanel useMemo dependency array.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • ecency/ecency-mobile#3272: Both PRs modify src/components/comment/view/commentView.tsx around the _totalVotes variable — that PR changes the fallback computation of _totalVotes, while this PR uses _totalVotes to drive accessibility labels, hints, and isClickable.

Poem

🐇 Hop, hop! The screen reader can see,
Each button now labeled with care from me.
"Send tip!" says the gift, "Reply!" says the thread,
"Post options" and "voters" — all properly said.
No widget left silent, no hint left behind,
A warren of access for all humankind! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding accessibility labels to post action-row and overflow-menu controls for screen reader users.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/action-row-accessibility

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/comment/view/commentView.tsx`:
- Around line 218-225: The accessibilityHint with the message "View voters" (id:
post.a11y_voters_hint) is shown regardless of whether the voters control is
actually interactive. Since the control only functions when _totalVotes is
greater than 0 (as checked on Line 214), make the accessibilityHint conditional
so it only renders when _totalVotes > 0. This ensures screen readers don't
mislead users by suggesting they can view voters when the control is
non-interactive. The accessibilityLabel reporting the vote count can remain
unconditional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5d6bc9ce-1b6d-4ac0-b306-9de19d1c96a7

📥 Commits

Reviewing files that changed from the base of the PR and between ac43361 and e7c7b2b.

📒 Files selected for processing (9)
  • node_modules
  • src/components/basicUIElements/view/textWithIcon/textWithIconView.tsx
  • src/components/comment/view/commentView.tsx
  • src/components/iconButton/view/iconButtonView.tsx
  • src/components/postCard/children/postCardActionsPanel.tsx
  • src/components/postCard/children/postCardHeader.tsx
  • src/components/postElements/headerDescription/view/postHeaderDescription.tsx
  • src/components/postView/view/postDisplayView.tsx
  • src/config/locales/en-US.json

Comment thread src/components/comment/view/commentView.tsx Outdated
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds screen-reader labels (accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityState) to the post action surface — votes, reblogs, comments, tip, edit, delete, replies toggle, and overflow menus — covering feed cards, single-post view, and comment/wave rows. The changes are additive: all new props are optional, leaving every existing call site unaffected.

  • Shared components (TextWithIcon, IconButton) now forward optional a11y props and correctly gate accessibilityRole="button" and accessibilityState behind an interactivity check, so purely decorative instances don't surface a spurious "dimmed" announcement to VoiceOver/TalkBack.
  • 16 ICU-plural locale strings added to en-US.json with correct one/other forms; each formatMessage call includes a matching defaultMessage fallback, so untranslated locales degrade gracefully to readable English.
  • intl is correctly added to the useMemo dependency array in postDisplayView.tsx, and commentView.tsx conditionally sets isClickable on the votes button based on vote count, aligning the interactive state with the actual pressability.

Confidence Score: 5/5

Safe to merge — all changes are purely additive a11y metadata with no behavioural side-effects on sighted users.

Every new prop is optional, guarded by an interactivity check, and the locale fallbacks ensure no raw key IDs leak to users. The useMemo dep fix is correct, and the isClickable conditional on the votes button in commentView tightens behaviour that was already implicit. No logic paths that affect data, navigation, or rendering have changed.

No files require special attention — the shared-component changes to TextWithIcon and IconButton are the widest-impact surface but both are correctly guarded.

Important Files Changed

Filename Overview
src/components/basicUIElements/view/textWithIcon/textWithIconView.tsx Adds optional accessibilityLabel/Hint props and correctly gates role+state behind _interactive guard so decorative instances don't announce as dimmed.
src/components/iconButton/view/iconButtonView.tsx Adds accessibilityLabel/Hint and conditionally sets role on onPress; correctly propagates disabled state through isLoading.
src/components/postCard/children/postCardActionsPanel.tsx Adds intl hook and labels for votes (outer TouchableOpacity), reblogs, comments, and tip; ICU plural labels correct.
src/components/postCard/children/postCardHeader.tsx Adds Post options label to the overflow dots IconButton; minimal, correct change.
src/components/postElements/headerDescription/view/postHeaderDescription.tsx Adds Post options label to dots button via existing injected intl; mirrors postCardHeader change correctly.
src/components/comment/view/commentView.tsx Labels added to votes (with conditional isClickable fix), reply count, tip, edit, delete, and replies-toggle buttons; intl already in scope.
src/components/postView/view/postDisplayView.tsx Labels added for votes, reblogs, comments (logged-in and logged-out paths), tips, and stats view; intl correctly added to useMemo deps.
src/config/locales/en-US.json 16 new a11y keys added with correct ICU plural syntax; all keys referenced at call sites with matching defaultMessage fallbacks.

Reviews (2): Last reviewed commit: "fix(a11y): address review — plural count..." | Re-trigger Greptile

Comment thread src/config/locales/en-US.json Outdated
Comment thread src/components/basicUIElements/view/textWithIcon/textWithIconView.tsx Outdated
…ode_modules

- Remove the accidentally-committed node_modules symlink (it broke CI's
  yarn install --frozen-lockfile; node_modules stays gitignored/untracked).
- Use ICU plural for count labels (votes/reblogs/comments/tips/views) so a count
  of 1 reads "1 vote" not "1 votes". en-US strings + inline defaultMessage
  fallbacks both updated.
- TextWithIcon: set accessibilityState only on interactive instances so
  decorative/display-only ones don't announce as "dimmed".
- IconButton: report disabled while isLoading, since onPress is suppressed then.
- commentView votes: gate isClickable + the "View voters" hint on _totalVotes > 0
  so a zero-vote control isn't announced as an actionable button.
@feruzm

feruzm commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

Pushed 9751612 addressing the review + the Lint CI failure:

  • Lint CI failure / Codex P1 (tracked node_modules symlink): root cause — a node_modules symlink was accidentally committed; CI's yarn install --frozen-lockfile choked on the dangling link. Removed it (git rm --cached); node_modules stays untracked/gitignored.
  • Greptile (singular/plural): count labels now use ICU plural — "1 vote" / "2 votes" — for votes/reblogs/comments/tips/views, in both the en-US strings and the inline defaultMessage fallbacks. Verified each parses (0/1/2 → "0 votes"/"1 vote"/"2 votes").
  • Greptile (accessibilityState on decorative TextWithIcon): state is now set only on interactive instances, so display-only ones no longer announce as "dimmed".
  • Codex P2 (IconButton isLoading): the disabled state now includes isLoading, so a spinning button isn't announced as an enabled no-op.
  • CodeRabbit (zero-vote voters control): isClickable and the "View voters" hint are now gated on _totalVotes > 0.

Prettier 2.8.8 ✓, eslint 0 errors ✓.

@feruzm feruzm merged commit 370ecd7 into development Jun 23, 2026
10 of 12 checks passed
@feruzm feruzm deleted the fix/action-row-accessibility branch June 23, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant