open-webui / open-webui/open-webui
feat: transcript is not navigable by screen reader — role="log" orphans listitems, and messages have no headings
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 153k
- Forks
- 22.3k
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 194
Description
Check Existing Issues
- I have searched all existing open AND closed issues and discussions and found none comparable to my request.
- I have checked whether this request is already implemented on the
devbranch or in the latest source.
Problem Description
I'm reporting this on behalf of a totally blind daily user (VoiceOver on macOS) who uses Open WebUI as her primary chat surface. Two things in the message transcript make it hard to navigate by screen reader. Both are confirmed present in 0.11.1's shipped bundle.
1. role="log" on the <ul> orphans its listitem children.
The transcript renders as:
<ul role="log" aria-live="polite" aria-relevant="additions" aria-atomic="false">
with children that are <div role="listitem">.
An explicit role="log" on a <ul> overrides that element's implicit list role. listitem requires a list (or group) parent, so every message ends up as an orphaned listitem with no owning list. The practical cost to a screen reader user is list navigation: no "list of N items" summary, and no item-by-item movement through messages. The live region works; the list is gone.
2. Message author names are not headings.
The name renders as:
<span id="response-message-model-name" class="line-clamp-1 ...">
There is no heading anywhere inside a message. With Chat Bubble UI off, the transcript reads as one continuous block with no jump targets — the headings rotor and VO-Cmd-Shift-H have nothing to land on. To find the start of a long reply you have to reach its end and scroll back.
Note these two compound: with no list semantics and no headings, there is no structural way to move between turns at all.
Proposed Solution
For the list semantics — keep the live region, but host it on a wrapping element so the <ul> can stay a list:
<section aria-live="polite" aria-relevant="additions" aria-atomic="false">
<ul>
<div role="listitem">…</div>
</ul>
</section>
aria-live applies to descendants, so additions inside the list are still announced. We do exactly this at runtime and it resolves the problem without any loss of announcement behaviour. (Making the children real <li> elements rather than role="listitem" divs would be even better, but is a larger change and not necessary to fix this.)
For the headings — render the author name as a heading, or add an sr-only heading at the start of each message. Either gives the rotor a per-turn target. A visually identical sr-only heading is the least disruptive option.
Alternatives Considered
We currently patch both client-side from static/loader.js, which the image ships empty and loads on every page. That works, but it is a per-deployment workaround for something every screen-reader user of Open WebUI hits, which is why I'd rather it were fixed upstream — we'd delete our patch.
Additional Context
Verified against ghcr.io/open-webui/open-webui:0.11.1 (sha256:6bb1fbe8ab0a…, upstream revision d3e8bf3405e848cfba377814d0aa7ba7290e414d) by reading the served bundle directly, not from our own notes.
Credit where due: 0.11.1's accessibility work is genuinely felt here. Hover-only buttons becoming keyboard-reachable was the single biggest improvement in months, and a closed sidebar no longer being announced removed real noise. This report is the remaining gap, not a complaint about the direction.
Happy to open a PR for either fix if that's useful — just say which shape you'd prefer for the heading (real heading vs sr-only).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating the transcript markup with the ul role="log" and the response-message-model-name span, then review the client-side workaround in static/loader.js. Keep live-region announcements while restoring list semantics, and provide a heading target for each message; verify the rendered transcript with a screen reader.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- html, javascript
- Domain
- accessibility, frontend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100