Skip to content

🗣️ a11y: Distinguish Conversation Headings for Screen Readers#12341

Merged
danny-avila merged 2 commits into
devfrom
dlew/screen-reader-conversation-headings
Mar 20, 2026
Merged

🗣️ a11y: Distinguish Conversation Headings for Screen Readers#12341
danny-avila merged 2 commits into
devfrom
dlew/screen-reader-conversation-headings

Conversation

@danny-avila

@danny-avila danny-avila commented Mar 20, 2026

Copy link
Copy Markdown
Owner

Summary

Continues #12154 (from @dlew) with review fixes applied.

Before, each message heading was just the user/agent name (e.g. "Dan Lew" or "Claude Sonnet"). Screen reader users navigating by headings saw identical labels with no way to distinguish position in the conversation.

Now, each <h2> includes a screen-reader-only prefix: "Prompt N:" or "Response N:", where N is the 1-based turn number. This is invisible visually but announced by screen readers in heading navigation mode.

Changes from original PR

  • Patch MessageParts.tsx — the Assistants/Azure Assistants endpoint render path was missing the sr-only prefix entirely
  • Guard negative depth — extract shared getMessageNumber helper that returns null for depth < 0, preventing "Prompt 0:" / "Response 0:" from surfacing
  • DRY — both getMessageAriaLabel and getHeaderPrefixForScreenReader now share the same depth computation via getMessageNumber
  • Remove unused lodash import_.isNil replaced with idiomatic == null check
  • Add unit tests — 10 tests covering all branches of both functions, including depth edge cases

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

  • All 10 new unit tests pass (client/src/utils/__tests__/messages.test.ts)
  • No visual change — prefix uses sr-only class
  • No linter errors introduced

Before, each message would have the heading of either the name of
the user or the name of the agent (e.g. "Dan Lew" or "Claude Sonnet").
If you tried to navigate that with a screen reader, you'd just see
a ton of headings switching back and forth between the two with no
way to figure out where in the conversation each is.

Now, we prefix each header with whether it's a "prompt" or "response",
plus we number them so that you can distinguish how far in the
conversation each part is.

(This is a screen reader only change - there's no visual difference.)
Copilot AI review requested due to automatic review settings March 20, 2026 20:12
@danny-avila danny-avila force-pushed the dlew/screen-reader-conversation-headings branch from 8db7434 to 8f11d80 Compare March 20, 2026 20:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves chat message heading accessibility by adding a screen-reader-only prefix that distinguishes prompts vs responses and includes an optional 1-based turn number derived from message.depth, making heading navigation meaningful for screen reader users.

Changes:

  • Added getHeaderPrefixForScreenReader() (and shared getMessageNumber() helper) to generate “Prompt N:” / “Response N:” prefixes and avoid numbering invalid depths.
  • Updated message heading renderers to include the sr-only prefix before the visual label.
  • Added unit tests for getMessageAriaLabel() and getHeaderPrefixForScreenReader().

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
client/src/utils/messages.ts Adds shared depth→turn-number helper and new screen-reader-only heading prefix utility.
client/src/utils/tests/messages.test.ts Adds unit tests for the updated/new message accessibility utilities.
client/src/components/Messages/ContentRender.tsx Injects sr-only heading prefix into message headings in this render path.
client/src/components/Chat/Messages/ui/MessageRender.tsx Injects sr-only heading prefix into message headings in this render path.
client/src/components/Chat/Messages/MessageParts.tsx Injects sr-only heading prefix into the Assistants/Azure Assistants render path headings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread client/src/utils/__tests__/messages.test.ts
@danny-avila danny-avila changed the title fix: distinguish message headings for screen readers 🗣️ a11y: Distinguish Conversation Headings for Screen Readers Mar 20, 2026
@danny-avila danny-avila force-pushed the dlew/screen-reader-conversation-headings branch 2 times, most recently from c9b8763 to bdd7edf Compare March 20, 2026 20:38
- Add sr-only heading prefix to MessageParts.tsx (Assistants endpoint path)
- Extract shared getMessageNumber helper to avoid DRY violation between
  getMessageAriaLabel and getHeaderPrefixForScreenReader
- Guard against depth < 0 producing "Prompt 0:" / "Response 0:"
- Remove unused lodash import
- Add unit tests covering all branches including depth edge cases
@danny-avila danny-avila force-pushed the dlew/screen-reader-conversation-headings branch from bdd7edf to 9ea62ec Compare March 20, 2026 20:39
@danny-avila danny-avila merged commit 6976414 into dev Mar 20, 2026
7 checks passed
@danny-avila danny-avila deleted the dlew/screen-reader-conversation-headings branch March 20, 2026 20:50
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…avila#12341)

* fix: distinguish message headings for screen readers

Before, each message would have the heading of either the name of
the user or the name of the agent (e.g. "Dan Lew" or "Claude Sonnet").
If you tried to navigate that with a screen reader, you'd just see
a ton of headings switching back and forth between the two with no
way to figure out where in the conversation each is.

Now, we prefix each header with whether it's a "prompt" or "response",
plus we number them so that you can distinguish how far in the
conversation each part is.

(This is a screen reader only change - there's no visual difference.)

* fix: patch MessageParts heading, guard negative depth, add tests

- Add sr-only heading prefix to MessageParts.tsx (Assistants endpoint path)
- Extract shared getMessageNumber helper to avoid DRY violation between
  getMessageAriaLabel and getHeaderPrefixForScreenReader
- Guard against depth < 0 producing "Prompt 0:" / "Response 0:"
- Remove unused lodash import
- Add unit tests covering all branches including depth edge cases

---------

Co-authored-by: Dan Lew <daniel@mightyacorn.com>
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.

3 participants