🔇 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

@ -45,7 +45,7 @@ const SettingsButton = ({
aria-label={`${text} ${endpoint.label}`}
>
<div className="flex w-[28px] items-center gap-1 whitespace-nowrap transition-all duration-300 ease-in-out group-hover:w-auto group-focus/button:w-auto">
<SettingsIcon className="h-4 w-4 flex-shrink-0" />
<SettingsIcon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
<span className="max-w-0 overflow-hidden whitespace-nowrap opacity-0 transition-all duration-300 ease-in-out group-hover:max-w-[100px] group-hover:opacity-100 group-focus/button:max-w-[100px] group-focus/button:opacity-100">
{text}
</span>

View file

@ -51,7 +51,10 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
) : null}
<span className="truncate text-left">{modelName}</span>
{isGlobal && (
<EarthIcon className="ml-auto size-4 flex-shrink-0 self-center text-green-400" />
<EarthIcon
className="ml-auto size-4 flex-shrink-0 self-center text-green-400"
aria-hidden="true"
/>
)}
</div>
{isSelected && (

View file

@ -172,7 +172,9 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
)}
<span>{modelName}</span>
</div>
{isGlobal && <EarthIcon className="ml-auto size-4 text-green-400" />}
{isGlobal && (
<EarthIcon className="ml-auto size-4 text-green-400" aria-hidden="true" />
)}
{selectedEndpoint === endpoint.value && selectedModel === modelId && (
<svg
width="16"

View file

@ -32,7 +32,7 @@ const PresetsMenu: FC = () => {
data-testid="presets-button"
className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
>
<BookCopy size={16} aria-label="Preset Icon" />
<BookCopy size={16} aria-hidden="true" />
</TooltipAnchor>
</Trigger>
<Portal>

View file

@ -22,7 +22,7 @@ export default function TitleButton({ primaryText = '', secondaryText = '' }) {
<span className="text-text-primary"> {primaryText} </span>
{!!secondaryText && <span className="text-token-text-secondary">{secondaryText}</span>}
</div>
<ChevronDown className="text-token-text-secondary size-4" />
<ChevronDown className="text-token-text-secondary size-4" aria-hidden="true" />
</button>
</Trigger>
);