🔇 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

@ -80,7 +80,7 @@ export default function AccessRolesPicker({
<span className="font-medium">
{selectedRoleInfo?.name || localize('com_ui_select')}
</span>
<ChevronDown className="h-4 w-4 text-text-secondary" />
<ChevronDown className="h-4 w-4 text-text-secondary" aria-hidden="true" />
</Ariakit.MenuButton>
}
items={dropdownItems}

View file

@ -241,7 +241,7 @@ export default function GenericGrantAccessDialog({
>
<div className="flex min-w-[32px] items-center justify-center gap-2 text-blue-500">
<span className="flex h-6 w-6 items-center justify-center">
<Share2Icon className="icon-md h-4 w-4" />
<Share2Icon className="icon-md h-4 w-4" aria-hidden="true" />
</span>
{totalCurrentShares > 0 && (
<Label className="text-sm font-medium text-text-secondary">{totalCurrentShares}</Label>
@ -256,7 +256,7 @@ export default function GenericGrantAccessDialog({
<OGDialogContent className="max-h-[90vh] w-11/12 overflow-y-auto md:max-w-3xl">
<OGDialogTitle>
<div className="flex items-center gap-2">
<Users className="h-5 w-5" />
<Users className="h-5 w-5" aria-hidden="true" />
{localize('com_ui_share_var', {
0: config?.getShareMessage(resourceName),
})}
@ -270,7 +270,7 @@ export default function GenericGrantAccessDialog({
{hasPeoplePickerAccess && (
<div className="space-y-2">
<h4 className="mb-2 flex items-center gap-2 text-sm font-medium text-text-primary">
<UserCheck className="h-4 w-4" />
<UserCheck className="h-4 w-4" aria-hidden="true" />
{localize('com_ui_user_group_permissions')} ( {allShares.length} )
</h4>
@ -295,7 +295,7 @@ export default function GenericGrantAccessDialog({
if (allShares.length === 0 && !hasChanges) {
return (
<div className="rounded-lg border-2 border-dashed border-border-light p-8 text-center">
<Users className="mx-auto h-8 w-8 text-text-primary" />
<Users className="mx-auto h-8 w-8 text-text-primary" aria-hidden="true" />
<p className="mt-2 text-sm text-text-primary">
{localize('com_ui_no_individual_access')}
</p>
@ -311,7 +311,7 @@ export default function GenericGrantAccessDialog({
{!hasAtLeastOneOwner && hasChanges && (
<div className="rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-center">
<div className="flex items-center justify-center gap-2 text-sm text-red-600 dark:text-red-400">
<UserX className="h-4 w-4" />
<UserX className="h-4 w-4" aria-hidden="true" />
{localize('com_ui_at_least_one_owner_required')}
</div>
</div>
@ -363,7 +363,11 @@ export default function GenericGrantAccessDialog({
: localize('com_ui_copy_url_to_clipboard')
}
>
{isCopying ? <CopyCheck className="h-4 w-4" /> : <Link className="h-4 w-4" />}
{isCopying ? (
<CopyCheck className="h-4 w-4" aria-hidden="true" />
) : (
<Link className="h-4 w-4" aria-hidden="true" />
)}
</Button>
)}
</div>

View file

@ -85,7 +85,10 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
{isLoading ? (
<Spinner className="absolute left-3 h-4 w-4" />
) : (
<Search className="absolute left-3 h-4 w-4 text-text-secondary group-focus-within:text-text-primary group-hover:text-text-primary" />
<Search
className="absolute left-3 h-4 w-4 text-text-secondary group-focus-within:text-text-primary group-hover:text-text-primary"
aria-hidden="true"
/>
)}
<Ariakit.Combobox
ref={inputRef}

View file

@ -38,7 +38,7 @@ export default function SelectedPrincipalsList({
return (
<div className={`space-y-3 ${className}`}>
<div className="rounded-lg border border-dashed border-border-medium py-8 text-center text-muted-foreground">
<Users className="mx-auto mb-2 h-8 w-8 opacity-50" />
<Users className="mx-auto mb-2 h-8 w-8 opacity-50" aria-hidden="true" />
<p className="mt-1 text-xs">{localize('com_ui_search_above_to_add_all')}</p>
</div>
</div>
@ -64,7 +64,7 @@ export default function SelectedPrincipalsList({
<span>{subtitle}</span>
{share.source === 'entra' && (
<>
<ExternalLink className="h-3 w-3" />
<ExternalLink className="h-3 w-3" aria-hidden="true" />
<span>{localize('com_ui_azure_ad')}</span>
</>
)}
@ -89,7 +89,7 @@ export default function SelectedPrincipalsList({
className="h-9 w-9 p-0 hover:border-destructive/10 hover:bg-destructive/10 hover:text-destructive"
aria-label={localize('com_ui_remove_user', { 0: displayName })}
>
<X className="h-4 w-4" />
<X className="h-4 w-4" aria-hidden="true" />
</Button>
</div>
</div>