🔇 fix: Hide Button Icons from Screen Readers (#10776)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled

If you've got a screen reader that is reading out the whole page,
each icon button (i.e., `<button><SVG></button>`) will have both
the button's aria-label read out as well as the title from the
SVG (which is usually just "image").

Since we are pretty good about setting aria-labels, we should instead
use `aria-hidden="true"` on these images, since they are not useful
to be read out.

I don't consider this a comprehensive review of all icons in the app,
but I knocked out all the low hanging fruit in this commit.
This commit is contained in:
Daniel Lew 2025-12-11 15:35:17 -06:00 committed by GitHub
parent b288d81f5a
commit 1143f73f59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
175 changed files with 340 additions and 183 deletions

View file

@ -91,7 +91,7 @@ export function SourceHovercard({
/>
<Ariakit.HovercardDisclosure className="ml-0.5 rounded-full text-text-primary focus:outline-none focus:ring-2 focus:ring-ring">
<VisuallyHidden>{localize('com_citation_more_details', { label })}</VisuallyHidden>
<ChevronDown className="icon-sm" />
<ChevronDown className="icon-sm" aria-hidden="true" />
</Ariakit.HovercardDisclosure>
<Ariakit.Hovercard

View file

@ -84,7 +84,7 @@ function SourceItem({ source, expanded = false }: SourceItemProps) {
<VisuallyHidden>
{localize('com_citation_more_details', { label: domain })}
</VisuallyHidden>
<ChevronDown className="icon-sm" />
<ChevronDown className="icon-sm" aria-hidden="true" />
</Ariakit.HovercardDisclosure>
<Ariakit.Hovercard
@ -298,7 +298,7 @@ const FileItem = React.memo(function FileItem({
<span className="truncate text-xs font-medium text-text-secondary">
{localize('com_sources_agent_file')}
</span>
{!isLocalFile && <Download className="ml-auto h-3 w-3" />}
{!isLocalFile && <Download className="ml-auto h-3 w-3" aria-hidden="true" />}
</div>
<div className="mt-1 min-w-0">
<span className="line-clamp-2 break-all text-left text-sm font-medium text-text-primary md:line-clamp-3">
@ -337,7 +337,7 @@ const FileItem = React.memo(function FileItem({
<span className="truncate text-xs font-medium text-text-secondary">
{localize('com_sources_agent_file')}
</span>
{!isLocalFile && <Download className="ml-auto h-3 w-3" />}
{!isLocalFile && <Download className="ml-auto h-3 w-3" aria-hidden="true" />}
</div>
<div className="mt-1 min-w-0">
<span className="line-clamp-2 break-all text-left text-sm font-medium text-text-primary md:line-clamp-3">
@ -428,7 +428,7 @@ const SourcesGroup = React.memo(function SourcesGroup({
className="rounded-full p-1 text-text-secondary hover:bg-surface-tertiary hover:text-text-primary"
aria-label={localize('com_ui_close')}
>
<X className="h-4 w-4" />
<X className="h-4 w-4" aria-hidden="true" />
</OGDialogClose>
</div>
<div className="flex-1 overflow-y-auto px-3 py-2">
@ -524,7 +524,7 @@ function FilesGroup({ files, messageId, conversationId, limit = 3 }: FilesGroupP
className="rounded-full p-1 text-text-secondary hover:bg-surface-tertiary hover:text-text-primary"
aria-label={localize('com_ui_close')}
>
<X className="h-4 w-4" />
<X className="h-4 w-4" aria-hidden="true" />
</OGDialogClose>
</div>
<div className="flex-1 overflow-y-auto px-3 py-2">
@ -549,7 +549,7 @@ function FilesGroup({ files, messageId, conversationId, limit = 3 }: FilesGroupP
function TabWithIcon({ label, icon }: { label: string; icon: React.ReactNode }) {
return (
<div className="flex items-center gap-2 rounded-md px-3 py-1 text-sm transition-colors hover:bg-surface-tertiary hover:text-text-primary">
{React.cloneElement(icon as React.ReactElement, { size: 14 })}
{React.cloneElement(icon as React.ReactElement, { size: 14, 'aria-hidden': true })}
<span>{label}</span>
</div>
);