fix(frontend): add aria-labels to icon-only buttons

Resolves QA report BUG #12, #13, #14, #15, #18 — icon-only buttons
across forms, sidebar, DataTable, and detail pages lacked accessible
names. Applied handler-based naming convention so the label matches
the prop / handler / state attribute, not the visual icon:

- type="submit" + isSubmitting → "Submit" / "Submitting…"
- onCancel + isCanceling → "Cancel" / "Cancelling…"
- onToggleFavorite + isFavorite → "Toggle favorite" + aria-pressed
- setTheme(value) + value → "<Value> theme"
- Pagination via table.firstPage()/etc → "First/Previous/Next/Last page"
- Row actions DropdownMenuTrigger → "Open menu" via existing sr-only convention

Verified: 0 ESLint errors, 508/508 tests pass, regression sweep across
9 pages (/dashboard, /flows, /templates, /knowledges, /resources,
/flows/:id, /settings/{providers,api-tokens,prompts}) returned 0
icon-only buttons without accessible name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-05-21 18:15:03 +07:00
parent de14bacb59
commit 9eee2b48a1
10 changed files with 24 additions and 0 deletions
@@ -333,18 +333,21 @@ export function MainSidebar() {
>
<TabsList className="h-7 p-0.5">
<TabsTrigger
aria-label="System theme"
className="h-6 px-2"
value="system"
>
<Monitor className="size-4" />
</TabsTrigger>
<TabsTrigger
aria-label="Light theme"
className="h-6 px-2"
value="light"
>
<Sun className="size-4" />
</TabsTrigger>
<TabsTrigger
aria-label="Dark theme"
className="h-6 px-2"
value="dark"
>
@@ -418,6 +421,8 @@ function FlowMenuItem({ activeFlowId, flow, isFavorite, onToggleFavorite }: Flow
</Link>
</SidebarMenuButton>
<SidebarMenuAction
aria-label="Toggle favorite"
aria-pressed={isFavorite}
className="data-[state=open]:bg-accent rounded-sm"
onClick={() => onToggleFavorite(flow.id)}
showOnHover
@@ -820,6 +820,7 @@ function DataTable<TData, TValue = unknown>({
)}
<div className="flex items-center gap-1">
<Button
aria-label="First page"
disabled={!table.getCanPreviousPage()}
onClick={() => table.firstPage()}
size="icon-xs"
@@ -828,6 +829,7 @@ function DataTable<TData, TValue = unknown>({
<ChevronsLeft />
</Button>
<Button
aria-label="Previous page"
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
size="icon-xs"
@@ -836,6 +838,7 @@ function DataTable<TData, TValue = unknown>({
<ChevronLeft />
</Button>
<Button
aria-label="Next page"
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
size="icon-xs"
@@ -844,6 +847,7 @@ function DataTable<TData, TValue = unknown>({
<ChevronRight />
</Button>
<Button
aria-label="Last page"
disabled={!table.getCanNextPage()}
onClick={() => table.lastPage()}
size="icon-xs"
@@ -761,6 +761,7 @@ export function FlowForm({
{!isLoading || isSubmitting ? (
<InputGroupButton
aria-label={isSubmitting ? 'Submitting…' : 'Submit'}
className="shrink-0"
disabled={isSubmitting || !isValid || upload.isUploading}
size="icon-xs"
@@ -771,6 +772,7 @@ export function FlowForm({
</InputGroupButton>
) : (
<InputGroupButton
aria-label={isCanceling ? 'Cancelling…' : 'Cancel'}
className="shrink-0"
disabled={isCanceling || !onCancel}
onClick={() => onCancel?.()}
@@ -114,6 +114,8 @@ function FlowTasksDropdown({ disabled, onChange, value }: FlowTasksDropdownProps
>
<PopoverTrigger asChild>
<Button
aria-label="Filter tasks"
aria-pressed={hasActiveFilters}
disabled={disabled}
size="icon"
variant="outline"
+2
View File
@@ -269,6 +269,8 @@ function Flow() {
)}
{flowId && !isMobile && (
<Button
aria-label="Toggle favorite"
aria-pressed={isFavoriteFlow(flowId)}
className="shrink-0"
onClick={() => toggleFavoriteFlow(flowId)}
size="icon"
+1
View File
@@ -420,6 +420,7 @@ function Flows() {
onClick={(e) => e.stopPropagation()}
variant="ghost"
>
<span className="sr-only">Open menu</span>
<Ellipsis />
</Button>
</DropdownMenuTrigger>
@@ -328,6 +328,7 @@ function Knowledges() {
onClick={(event) => event.stopPropagation()}
variant="ghost"
>
<span className="sr-only">Open menu</span>
<Ellipsis />
</Button>
</DropdownMenuTrigger>
@@ -505,6 +505,7 @@ function Resources() {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-label="Column settings"
className="ml-auto"
size="icon"
variant="outline"
@@ -211,6 +211,7 @@ function CreateRowActions({
return (
<div className="flex justify-end">
<Button
aria-label={isLoading ? 'Submitting…' : 'Submit'}
className="shrink-0"
disabled={isLoading || !isValid}
onClick={onSubmit}
@@ -220,6 +221,7 @@ function CreateRowActions({
{isLoading ? <Loader2 className="animate-spin" /> : <Check />}
</Button>
<Button
aria-label="Cancel"
className="shrink-0"
onClick={onCancel}
size="icon-sm"
@@ -247,6 +249,7 @@ function EditRowActions({
return (
<div className="flex justify-end">
<Button
aria-label={isLoading ? 'Submitting…' : 'Submit'}
className="shrink-0"
disabled={isLoading || !isValid}
onClick={onSubmit}
@@ -256,6 +259,7 @@ function EditRowActions({
{isLoading ? <Loader2 className="animate-spin" /> : <Check />}
</Button>
<Button
aria-label="Cancel"
className="shrink-0"
onClick={onCancel}
size="icon-sm"
@@ -530,6 +534,7 @@ function SettingsAPITokens() {
<div className="flex items-center gap-2">
<code className="text-sm">{tokenId}</code>
<Button
aria-label="Copy token ID"
className="size-6 p-0"
onClick={() => handleCopyTokenId(tokenId)}
variant="ghost"
@@ -175,6 +175,7 @@ function Templates() {
onClick={(e) => e.stopPropagation()}
variant="ghost"
>
<span className="sr-only">Open menu</span>
<Ellipsis />
</Button>
</DropdownMenuTrigger>