Skip to content

Commit 5166702

Browse files
committed
🤖 fix(site): a11y polish on the AI providers admin form and list
Three accessibility gaps surfaced by the review pass on the AI providers admin UI: * FormField now wires its optional `description` to the input's `aria-describedby` chain via a stable `${id}-description` id. The description was previously rendered as an unanchored `<div>`, so screen readers never announced it when the input received focus. The chain composes with the existing helper/error ids. * The providers `<Table>` carries an `aria-label="AI providers"` so screen readers can name it, per the site/AGENTS.md table rule. * `ProviderRow` switches from a bare `onClick` + `cursor-pointer` className to the shared `useClickableTableRow` hook used by other clickable rows in the app (e.g. TemplateVersions). That gives the row a button role, a tab stop, Enter/Space activation, a focus ring, and middle-click handling, so keyboard-only users can navigate to and activate provider rows.
1 parent d13c04e commit 5166702

3 files changed

Lines changed: 17 additions & 11 deletions

File tree

site/src/components/FormField/FormField.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,21 @@ export const FormField: FC<FormFieldProps> = ({
2121
const id = inputProps.id ?? generatedId;
2222
const errorId = `${id}-error`;
2323
const helperId = `${id}-helper`;
24+
const descriptionId = `${id}-description`;
25+
const describedBy = [
26+
description ? descriptionId : null,
27+
field.error ? errorId : field.helperText ? helperId : null,
28+
]
29+
.filter(Boolean)
30+
.join(" ");
2431

2532
return (
2633
<div className="flex flex-col gap-2">
2734
<Label htmlFor={id}>{label}</Label>
2835
{description && (
29-
<div className="text-xs text-content-secondary">{description}</div>
36+
<div id={descriptionId} className="text-xs text-content-secondary">
37+
{description}
38+
</div>
3039
)}
3140
<Input
3241
name={field.name}
@@ -36,9 +45,7 @@ export const FormField: FC<FormFieldProps> = ({
3645
{...inputProps}
3746
id={id}
3847
aria-invalid={field.error}
39-
aria-describedby={
40-
field.error ? errorId : field.helperText ? helperId : undefined
41-
}
48+
aria-describedby={describedBy || undefined}
4249
className={cn(field.error && "border-border-destructive", className)}
4350
/>
4451
{field.error ? (

site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const ProvidersPageView: React.FC<ProvidersPageViewProps> = ({
5151
conversations.
5252
</PageHeaderSubtitle>
5353
</PageHeader>
54-
<Table className="table-fixed">
54+
<Table className="table-fixed" aria-label="AI providers">
5555
<TableHeader>
5656
<TableRow>
5757
<TableHead>Name</TableHead>

site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AIProvider } from "#/api/typesGenerated";
33
import { Avatar } from "#/components/Avatar/Avatar";
44
import { AvatarData } from "#/components/Avatar/AvatarData";
55
import { TableCell, TableRow } from "#/components/Table/Table";
6+
import { useClickableTableRow } from "#/hooks/useClickableTableRow";
67
import { ProviderIcon } from "./ProviderIcon";
78

89
type ProviderRowProps = {
@@ -14,13 +15,11 @@ export const ProviderRow: React.FC<ProviderRowProps> = ({
1415
provider,
1516
onClick,
1617
}) => {
18+
const clickableProps = useClickableTableRow({
19+
onClick: () => onClick?.(),
20+
});
1721
return (
18-
<TableRow
19-
key={provider.name}
20-
hover
21-
className="cursor-pointer"
22-
onClick={() => onClick?.()}
23-
>
22+
<TableRow key={provider.name} {...clickableProps}>
2423
<TableCell>
2524
<AvatarData
2625
title={provider.display_name}

0 commit comments

Comments
 (0)