Severity: medium · Effort: S · Aspect: ux
Goal
The main model search box on the llm-timeline homepage (and its "add model to compare" variant) should expose a real accessible name via aria-label or an associated <label>, not rely on placeholder alone.
Context & Why
https://llm-timeline.duyet.net is a timeline/filter UI whose primary interaction is its search box (SearchAutocomplete, used by components/filters.tsx on the homepage, and a second instance in components/compare/ModelSelector.tsx on the compare view). Both inputs set only a placeholder ("Search models, organizations..." / "Search models by name or organization...") with no aria-label/aria-labelledby/<label>. Per WCAG 2.1 SC 1.3.1 / 4.1.2 (and browser/AT behavior), a placeholder is not a reliable substitute for an accessible name — it disappears once text is typed and is not consistently exposed to all assistive technology in the same way a label is. This is the single most-used control on the app's landing page, so it directly affects screen-reader / voice-control users' ability to identify the search field, more than a secondary control would.
Evidence
apps/llm-timeline/components/SearchAutocomplete.tsx:178-205 — the input has role="combobox", aria-expanded, aria-controls, aria-autocomplete, aria-activedescendant (a genuinely well-built combobox pattern otherwise) but no aria-label/aria-labelledby:
<input
ref={inputRef}
type="text"
placeholder={placeholder}
...
role="combobox"
aria-expanded={showSuggestions}
aria-controls="search-suggestions"
aria-autocomplete="list"
aria-activedescendant={...}
/>
apps/llm-timeline/components/filters.tsx:82 passes placeholder="Search models, organizations..." into it (homepage instance) with no label prop supplied.
apps/llm-timeline/components/compare/ModelSelector.tsx:72-80 — the compare-view variant, a plain shadcn <Input type="text" placeholder="Search models by name or organization..." .../>, same gap.
- Contrast: other search inputs in this monorepo do this correctly, e.g.
apps/home/src/components/urls/UrlsSearchBar.tsx:25-28 (aria-label="Search by path, URL, or description") and apps/homelab/components/dashboard/ServicesStatus.tsx:48-53 (aria-label="Search services").
Suggested approach
Add an aria-label prop threaded through SearchAutocomplete (default it to the same text as placeholder if no explicit label is passed, so existing call sites keep working with minimal changes), and pass an explicit aria-label on the plain <Input> in ModelSelector.tsx. This is the fix-once location: SearchAutocomplete.tsx's prop list, since filters.tsx and any future consumer inherit the fix automatically.
Validation
Run pnpm exec axe or a browser accessibility inspector against https://llm-timeline.duyet.net and confirm the search input now reports a non-empty "Accessible Name" (e.g., via Chrome DevTools Accessibility pane), matching the label text.
Filed by automated multi-repo audit (2026-07-16) — aspect: ux (UI/UX and accessibility).
Severity: medium · Effort: S · Aspect: ux
Goal
The main model search box on the llm-timeline homepage (and its "add model to compare" variant) should expose a real accessible name via
aria-labelor an associated<label>, not rely onplaceholderalone.Context & Why
https://llm-timeline.duyet.netis a timeline/filter UI whose primary interaction is its search box (SearchAutocomplete, used bycomponents/filters.tsxon the homepage, and a second instance incomponents/compare/ModelSelector.tsxon the compare view). Both inputs set only aplaceholder("Search models, organizations..." / "Search models by name or organization...") with noaria-label/aria-labelledby/<label>. Per WCAG 2.1 SC 1.3.1 / 4.1.2 (and browser/AT behavior), aplaceholderis not a reliable substitute for an accessible name — it disappears once text is typed and is not consistently exposed to all assistive technology in the same way a label is. This is the single most-used control on the app's landing page, so it directly affects screen-reader / voice-control users' ability to identify the search field, more than a secondary control would.Evidence
apps/llm-timeline/components/SearchAutocomplete.tsx:178-205— the input hasrole="combobox",aria-expanded,aria-controls,aria-autocomplete,aria-activedescendant(a genuinely well-built combobox pattern otherwise) but noaria-label/aria-labelledby:apps/llm-timeline/components/filters.tsx:82passesplaceholder="Search models, organizations..."into it (homepage instance) with no label prop supplied.apps/llm-timeline/components/compare/ModelSelector.tsx:72-80— the compare-view variant, a plain shadcn<Input type="text" placeholder="Search models by name or organization..." .../>, same gap.apps/home/src/components/urls/UrlsSearchBar.tsx:25-28(aria-label="Search by path, URL, or description") andapps/homelab/components/dashboard/ServicesStatus.tsx:48-53(aria-label="Search services").Suggested approach
Add an
aria-labelprop threaded throughSearchAutocomplete(default it to the same text asplaceholderif no explicit label is passed, so existing call sites keep working with minimal changes), and pass an explicitaria-labelon the plain<Input>inModelSelector.tsx. This is the fix-once location:SearchAutocomplete.tsx's prop list, sincefilters.tsxand any future consumer inherit the fix automatically.Validation
Run
pnpm exec axeor a browser accessibility inspector againsthttps://llm-timeline.duyet.netand confirm the search input now reports a non-empty "Accessible Name" (e.g., via Chrome DevTools Accessibility pane), matching the label text.Filed by automated multi-repo audit (2026-07-16) — aspect: ux (UI/UX and accessibility).