Skip to content

Accessibility fixes for screen reader and keyboard users - #1380

Merged
dimdenGD merged 4 commits into
dimdenGD:masterfrom
2three1y:accessibility-fixes
Nov 1, 2026
Merged

dimdenGD merged 4 commits into
dimdenGD:masterfrom
2three1y:accessibility-fixes

Conversation

@2three1y

Copy link
Copy Markdown
Contributor

Hi! I use VoiceOver and I really like OldTwitter, but a lot of it is hard to use with a screen reader right now. For example, the reply, retweet and like buttons are spans with no name, so VoiceOver just reads "0 0 0" and I can't tell which is which or press them from the keyboard. This PR fixes that and some other things. I tried to keep every change small and not change how anything looks.

What changed

  • Tweet actions (reply, retweet, like/favorite, bookmark, more, views, download, remove bookmark): they're now buttons for screen readers (role="button"), you can Tab to them, Enter and Space work, and each one has a label with its count, like "Reply, 3". Like, retweet and bookmark also say if they're on (aria-pressed). The labels update when the count or state changes. Items in the retweet and "more" menus are buttons too.
  • See new tweets: it's now a focusable button, and its label includes how many new tweets there are. When you press it, focus moves to the first new tweet and it announces "5 new tweets shown". If "update timeline automatically" is on, it doesn't move focus.
  • Compose boxes: textareas keep their placeholder as a label, so the name doesn't disappear once you start typing. The character counter is announced when you get to 20 left, 0 left, and when you go over the limit (not on every key).
  • Modals: the modal is marked as a dialog, focus moves into it when it opens, the × is a real "Close" button you can reach with Tab, and focus goes back to where you were when it closes.
  • Headings: one h1 per page. Sidebar titles (Trends, Who to follow, You may also like) are level 2, and the numbers and labels in the profile card aren't headings anymore. Home, Notifications, Search and Tweet pages get a visually hidden h1.
  • Tabs and filters: the active profile tab and the active navbar link say aria-current, and search filters (Popular, Live, People…) say which one is selected.
  • Time links: the "5m" links have the full date as their label.
  • Top bar: the Messages link has an href so you can Tab to it (the click handler already calls preventDefault, so it still opens the DM popup). The profile picture in the top bar works as an "Account menu" button from the keyboard and says if the menu is open. Refresh and Load more are buttons too.
  • Small bug fix: the retweet tooltip said "(L)" but the hotkey is T.

How it works

Most of this is in one new file, scripts/accessibility.js, injected with the other page scripts. It adds the roles, labels and states and keeps them updated with a MutationObserver, so I didn't have to touch every place that builds or updates a tweet. It also adds one polite live region for the announcements. The rest are small edits: createModal in helpers.js, the new tweets button in layouts/home, heading attributes in the layout HTML, and a hidden-text class in header/style.css. New strings are only in the English locale, and other languages fall back to English like they already do.

Testing

  • Loaded the branch as an unpacked extension in Chrome, signed in, and checked the accessibility tree. Tweet buttons come out named like "Reply, 0", "More" is a button, time links have the full date, the compose box is named "What's happening?", and the top bar avatar is an "Account menu" button.
  • Keyboard: Enter on Reply opens the reply box. Space on More opens the menu. Enter on See new tweets moved focus to the first new one and announced it. Closing the new tweet modal with Esc or Close puts focus back on the Tweet button.
  • The compose counter announced 18 left, 0 left and the over-the-limit message. I never sent anything.
  • npm test passes, and node --check passes on the changed scripts.
  • I didn't test Firefox.

Suggestions (not in this PR)

  • The single-letter hotkeys are on by default and can clash with screen reader keys. Maybe turn them off by default for new installs, or add a hint about "Disable all hotkeys".
  • Moving the active tweet with S/W could also move keyboard focus, so screen readers follow it.
  • Notifications items aren't focusable or headed yet.

Tweet action spans (reply, retweet, like, bookmark, more) now have
role=button, are reachable with Tab, work with Enter/Space and get an
aria-label that includes the count ("Reply, 3"). Like, retweet and
bookmark expose aria-pressed. Also covers search filters (aria-pressed),
profile tabs and navbar links (aria-current), time links (full date
as label), textareas (placeholder kept as label), the character counter
(announced at 20 left, 0 left and over the limit) and one polite live
region for announcements.

Also fixes the retweet tooltip showing (L) instead of (T).
createModal now marks the content as a modal dialog, moves focus into it,
gives the close control a button role and a Close label, and returns
focus to where it was when the modal closes.
The bar is now a focusable button with the number of new tweets in its
label. When a person presses it, focus moves to the first new tweet and
the count is announced. Automatic timeline updates don't move focus.
One h1 per page: sidebar titles are now level 2 and the numbers and
labels in the profile card are no longer headings. Home, Notifications,
Search and Tweet pages get a visually hidden h1. Profile pictures in the
sidebar card and the top bar get labels, the top bar avatar works as a
menu button from the keyboard, and Messages gets an href so it is
reachable with Tab.
@dimdenGD
dimdenGD merged commit f7f0971 into dimdenGD:master Nov 1, 2026
2 checks passed
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.

2 participants