🔇 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

@ -74,6 +74,7 @@ const AgentHandoff: React.FC<AgentHandoffProps> = ({ name, args: _args = '' }) =
{hasInfo && (
<ChevronDown
className={cn('ml-1 h-3 w-3 transition-transform', showInfo && 'rotate-180')}
aria-hidden="true"
/>
)}
</div>

View file

@ -3,7 +3,7 @@ import { X } from 'lucide-react';
export default function CancelledIcon() {
return (
<div className="flex h-full w-full items-center justify-center rounded-full bg-transparent text-text-secondary">
<X className="size-4" />
<X className="size-4" aria-hidden="true" />
</div>
);
}

View file

@ -204,7 +204,7 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
className="h-10 w-10 p-0 hover:bg-surface-hover"
aria-label={localize('com_ui_close')}
>
<X className="size-7 sm:size-6" />
<X className="size-7 sm:size-6" aria-hidden="true" />
</Button>
}
/>
@ -219,7 +219,7 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
className="h-10 w-10 p-0"
aria-label={localize('com_ui_reset_zoom')}
>
<RotateCcw className="size-6" />
<RotateCcw className="size-6" aria-hidden="true" />
</Button>
}
/>
@ -233,7 +233,7 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
className="h-10 w-10 p-0"
aria-label={localize('com_ui_download')}
>
<ArrowDownToLine className="size-6" />
<ArrowDownToLine className="size-6" aria-hidden="true" />
</Button>
}
/>
@ -247,9 +247,9 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
aria-label={imageDetailsLabel}
>
{isPromptOpen ? (
<PanelLeftOpen className="size-7 sm:size-6" />
<PanelLeftOpen className="size-7 sm:size-6" aria-hidden="true" />
) : (
<PanelLeftClose className="size-7 sm:size-6" />
<PanelLeftClose className="size-7 sm:size-6" aria-hidden="true" />
)}
</Button>
}
@ -322,7 +322,7 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
variant="ghost"
className="h-12 w-12 p-0"
>
<X className="size-6" />
<X className="size-6" aria-hidden="true" />
</Button>
</div>

View file

@ -157,7 +157,7 @@ const EditTextPart = ({
{part.type === ContentTypes.THINK && (
<div className="mt-2 flex items-center gap-1.5 text-xs text-text-secondary">
<span className="flex gap-2 rounded-lg bg-surface-tertiary px-1.5 py-1 font-medium">
<Lightbulb className="size-3.5" />
<Lightbulb className="size-3.5" aria-hidden="true" />
{localize('com_ui_thoughts')}
</span>
</div>
@ -165,7 +165,7 @@ const EditTextPart = ({
{part.type !== ContentTypes.THINK && (
<div className="mt-2 flex items-center gap-1.5 text-xs text-text-secondary">
<span className="flex gap-2 rounded-lg bg-surface-tertiary px-1.5 py-1 font-medium">
<MessageSquare className="size-3.5" />
<MessageSquare className="size-3.5" aria-hidden="true" />
{localize('com_ui_response')}
</span>
</div>

View file

@ -71,12 +71,16 @@ export const ThinkingButton = memo(
)}
>
<span className="relative mr-1.5 inline-flex h-[18px] w-[18px] items-center justify-center">
<Lightbulb className="icon-sm absolute text-text-secondary opacity-100 transition-opacity group-hover/button:opacity-0" />
<Lightbulb
className="icon-sm absolute text-text-secondary opacity-100 transition-opacity group-hover/button:opacity-0"
aria-hidden="true"
/>
<ChevronDown
className={cn(
'icon-sm absolute transform-gpu text-text-primary opacity-0 transition-all duration-300 group-hover/button:opacity-100',
isExpanded && 'rotate-180',
)}
aria-hidden="true"
/>
</span>
{label}

View file

@ -98,9 +98,9 @@ export default function ProgressText({
<span className={showShimmer ? 'shimmer' : ''}>{text}</span>
{hasInput &&
(isExpanded ? (
<ChevronUp className="size-4 shrink-0 translate-y-[1px]" />
<ChevronUp className="size-4 shrink-0 translate-y-[1px]" aria-hidden="true" />
) : (
<ChevronDown className="size-4 shrink-0 translate-y-[1px]" />
<ChevronDown className="size-4 shrink-0 translate-y-[1px]" aria-hidden="true" />
))}
</button>
</Wrapper>

View file

@ -236,7 +236,7 @@ export default function ToolCall({
</Button>
</div>
<p className="flex items-center text-xs text-text-warning">
<TriangleAlert className="mr-1.5 inline-block h-4 w-4" />
<TriangleAlert className="mr-1.5 inline-block h-4 w-4" aria-hidden="true" />
{localize('com_assistants_allow_sites_you_trust')}
</p>
</div>