🔇 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

@ -66,7 +66,7 @@ function Artifacts() {
setValue={handleToggle}
label={localize('com_ui_artifacts')}
isCheckedClassName="border-amber-600/40 bg-amber-500/10 hover:bg-amber-700/10"
icon={<WandSparkles className="icon-md" />}
icon={<WandSparkles className="icon-md" aria-hidden="true" />}
/>
{isEnabled && (
@ -79,7 +79,7 @@ function Artifacts() {
)}
onClick={(e) => e.stopPropagation()}
>
<ChevronDown className="ml-1 h-4 w-4 text-text-secondary md:ml-0" />
<ChevronDown className="ml-1 h-4 w-4 text-text-secondary md:ml-0" aria-hidden="true" />
</Ariakit.MenuButton>
<Ariakit.Menu

View file

@ -62,9 +62,9 @@ const ArtifactsSubMenu = React.forwardRef<HTMLDivElement, ArtifactsSubMenuProps>
}
>
<div className="flex items-center gap-2">
<WandSparkles className="icon-md" />
<WandSparkles className="icon-md" aria-hidden="true" />
<span>{localize('com_ui_artifacts')}</span>
{isEnabled && <ChevronRight className="ml-auto h-3 w-3" />}
{isEnabled && <ChevronRight className="ml-auto h-3 w-3" aria-hidden="true" />}
</div>
<button
type="button"

View file

@ -29,7 +29,7 @@ function CodeInterpreter() {
setValue={debouncedChange}
label={localize('com_assistants_code_interpreter')}
isCheckedClassName="border-purple-600/40 bg-purple-500/10 hover:bg-purple-700/10"
icon={<TerminalSquareIcon className="icon-md" />}
icon={<TerminalSquareIcon className="icon-md" aria-hidden="true" />}
/>
)
);

View file

@ -41,9 +41,9 @@ const CollapseChat = ({
)}
>
{isCollapsed ? (
<ChevronUp className="h-full w-full" />
<ChevronUp className="h-full w-full" aria-hidden="true" />
) : (
<ChevronDown className="h-full w-full" />
<ChevronDown className="h-full w-full" aria-hidden="true" />
)}
</button>
}

View file

@ -33,7 +33,7 @@ export default function SourceIcon({
return (
<div className={cn(className, sourceToClassname[FileSources.execute_code] ?? '')}>
<span className="flex items-center justify-center">
<Terminal className="h-3 w-3" />
<Terminal className="h-3 w-3" aria-hidden="true" />
</span>
</div>
);
@ -43,7 +43,7 @@ export default function SourceIcon({
return (
<div className={cn(className, sourceToClassname[source] ?? '')}>
<span className="flex items-center justify-center">
<Type className="h-3 w-3" />
<Type className="h-3 w-3" aria-hidden="true" />
</span>
</div>
);
@ -53,7 +53,7 @@ export default function SourceIcon({
return (
<div className={cn(className, sourceToClassname[source] ?? '')}>
<span className="flex items-center justify-center">
<Database className="h-3 w-3" />
<Database className="h-3 w-3" aria-hidden="true" />
</span>
</div>
);

View file

@ -68,7 +68,7 @@ export const columns: ColumnDef<TFile>[] = [
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{localize('com_ui_name')}
<ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
<ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" aria-hidden="true" />
</Button>
);
},
@ -107,7 +107,7 @@ export const columns: ColumnDef<TFile>[] = [
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
>
{localize('com_ui_date')}
<ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
<ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" aria-hidden="true" />
</Button>
);
},
@ -160,7 +160,7 @@ export const columns: ColumnDef<TFile>[] = [
}
return (
<div className="flex flex-wrap items-center gap-2">
<Database className="icon-sm text-cyan-700" />
<Database className="icon-sm text-cyan-700" aria-hidden="true" />
{localize('com_ui_host')}
</div>
);
@ -204,7 +204,7 @@ export const columns: ColumnDef<TFile>[] = [
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{localize('com_ui_size')}
<ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
<ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" aria-hidden="true" />
</Button>
);
},

View file

@ -127,7 +127,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
aria-label={localize('com_files_filter_by')}
className={cn('min-w-[40px]', isSmallScreen && 'px-2 py-1')}
>
<ListFilter className="size-3.5 sm:size-4" />
<ListFilter className="size-3.5 sm:size-4" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent

View file

@ -41,9 +41,9 @@ export function SortFilterHeader<TData, TValue>({
>
<span>{title}</span>
{column.getIsFiltered() ? (
<ListFilter className="icon-sm ml-2 text-muted-foreground/70" />
<ListFilter className="icon-sm ml-2 text-muted-foreground/70" aria-hidden="true" />
) : (
<ListFilter className="icon-sm ml-2 opacity-30" />
<ListFilter className="icon-sm ml-2 opacity-30" aria-hidden="true" />
)}
{(() => {
const sortState = column.getIsSorted();
@ -95,7 +95,10 @@ export function SortFilterHeader<TData, TValue>({
column.setFilterValue(value);
}}
>
<ListFilter className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
<ListFilter
className="mr-2 h-3.5 w-3.5 text-muted-foreground/70"
aria-hidden="true"
/>
{filterValue}
</DropdownMenuItem>
);
@ -112,7 +115,7 @@ export function SortFilterHeader<TData, TValue>({
column.setFilterValue(undefined);
}}
>
<FilterX className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
<FilterX className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" aria-hidden="true" />
{localize('com_ui_show_all')}
</DropdownMenuItem>
)}

View file

@ -58,7 +58,7 @@ export default function HeaderOptions({
data-testid="parameters-button"
className="inline-flex size-10 items-center justify-center rounded-lg 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"
>
<Settings2 size={16} aria-label="Settings/Parameters Icon" />
<Settings2 size={16} aria-hidden="true" />
</TooltipAnchor>
)}
</div>

View file

@ -168,7 +168,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
render: (props) => (
<div {...props}>
<div className="flex items-center gap-2">
<Globe className="icon-md" />
<Globe className="icon-md" aria-hidden="true" />
<span>{localize('com_ui_web_search')}</span>
</div>
<div className="flex items-center gap-1">
@ -188,7 +188,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
ref={searchMenuTriggerRef}
>
<div className="h-4 w-4">
<Settings className="h-4 w-4" />
<Settings className="h-4 w-4" aria-hidden="true" />
</div>
</button>
)}
@ -222,7 +222,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
render: (props) => (
<div {...props}>
<div className="flex items-center gap-2">
<TerminalSquareIcon className="icon-md" />
<TerminalSquareIcon className="icon-md" aria-hidden="true" />
<span>{localize('com_assistants_code_interpreter')}</span>
</div>
<div className="flex items-center gap-1">
@ -242,7 +242,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
aria-label="Configure code interpreter"
>
<div className="h-4 w-4">
<Settings className="h-4 w-4" />
<Settings className="h-4 w-4" aria-hidden="true" />
</div>
</button>
)}
@ -310,7 +310,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
)}
>
<div className="flex w-full items-center justify-center gap-2">
<Settings2 className="icon-md" />
<Settings2 className="icon-md" aria-hidden="true" />
</div>
</Ariakit.MenuButton>
}

View file

@ -29,7 +29,7 @@ function WebSearch() {
setValue={debouncedChange}
label={localize('com_ui_search')}
isCheckedClassName="border-blue-600/40 bg-blue-500/10 hover:bg-blue-700/10"
icon={<Globe className="icon-md" />}
icon={<Globe className="icon-md" aria-hidden="true" />}
/>
)
);