From 1ab75147410e7d216d2973a1d8b4897e4e5bcc08 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 24 Sep 2026 23:39:43 -0400 Subject: [PATCH] fix(a11y): open a message image from the keyboard (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Timeline images were focusable (tabIndex 0) but only opened on click, so a keyboard user could Tab to one and do nothing with it, and screen readers heard a plain image. They are now role="button" labelled "View image: " and open the viewer on Enter or Space. Focus already returns to the image when the viewer closes (checked for mouse and keyboard). Verified in Chromium: Tab → Enter opens the viewer, Escape returns focus to "View image: red.png", Space opens it again; chromium e2e suite 19 passed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/components/message/content/ImageContent.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/components/message/content/ImageContent.tsx b/src/app/components/message/content/ImageContent.tsx index 8d1c5f2c1..47ece05e1 100644 --- a/src/app/components/message/content/ImageContent.tsx +++ b/src/app/components/message/content/ImageContent.tsx @@ -47,6 +47,9 @@ type RenderImageProps = { onError: () => void; onClick: () => void; tabIndex: number; + role: 'button'; + 'aria-label': string; + onKeyDown: (evt: React.KeyboardEvent) => void; }; export type ImageContentProps = { body: string; @@ -188,6 +191,15 @@ export const ImageContent = as<'div', ImageContentProps>( onError: handleError, onClick: () => (onOpenViewer ? onOpenViewer() : setViewer(true)), tabIndex: 0, + // [Gitea #164] Keyboard users could Tab to the image but not open it. + role: 'button', + 'aria-label': `View image: ${body}`, + onKeyDown: (evt) => { + if (evt.key !== 'Enter' && evt.key !== ' ') return; + evt.preventDefault(); + if (onOpenViewer) onOpenViewer(); + else setViewer(true); + }, })} )}