Skip to content

feat: accessible search combobox#519

Open
NnamdiCyber wants to merge 1 commit into
Predictify-org:mainfrom
NnamdiCyber:task/search-combobox
Open

feat: accessible search combobox#519
NnamdiCyber wants to merge 1 commit into
Predictify-org:mainfrom
NnamdiCyber:task/search-combobox

Conversation

@NnamdiCyber

Copy link
Copy Markdown

Summary

Implements app/components/SearchInput.tsx — a general-purpose accessible
search input following the WAI-ARIA 1.2 combobox pattern (§5.9).

Closes #477

Changes

File Change
app/components/SearchInput.tsx New combobox component
app/components/__tests__/SearchInput.test.tsx ~50 focused tests
docs/SEARCH_INPUT.md API & usage documentation

ARIA wiring

  • role="combobox" with aria-expanded, aria-haspopup="listbox",
    aria-autocomplete="list", aria-controls, aria-activedescendant
  • role="option" + aria-selected on each listbox item
  • aria-live="polite" status region announces result counts, loading, empty states
  • Clear button: aria-label="Clear search", keyboard-reachable

Keyboard interaction

Key Behaviour
ArrowDown Open listbox; next option (wraps)
ArrowUp Previous option (wraps)
Home / End First / last option
Enter Confirm highlighted option or submit free-text
Escape Close listbox; second press clears value
Tab Close listbox, natural focus flow

Tests

~50 cases: full ARIA wiring, all keyboard paths with wrap-around, click/hover,
outside-click close, clear button focus return, isLoading, no-results, controlled
vs uncontrolled, maxSuggestions, disabled, live-region, snapshot.

Run with: pnpm test app/components/__tests__/SearchInput.test.tsx

Docs

docs/SEARCH_INPUT.md — props table, keyboard table, usage examples.

Implements app/components/SearchInput.tsx following the WAI-ARIA 1.2
combobox pattern (§5.9) with full keyboard navigation.

ARIA wiring
- role=combobox on input; aria-expanded, aria-haspopup=listbox,
  aria-autocomplete=list, aria-controls, aria-activedescendant
- role=option + aria-selected on each listbox item
- aria-live=polite status region announces result counts, loading,
  and empty states to screen-reader users
- Clear button has aria-label='Clear search' and is tab-accessible

Keyboard interaction
- ArrowDown / ArrowUp  navigate options with wrap-around
- Home / End           jump to first / last option
- Enter               confirm highlighted option or submit free-text
- Escape              close listbox; second press clears value
- Tab                 close listbox, natural focus flow

API
- Controlled (value + onChange) and uncontrolled modes
- suggestions[], onSelect, onSubmit, onOpen, onClose callbacks
- isLoading, disabled, maxSuggestions, aria-label, aria-labelledby props
- Design-system tokens (bg-popover, accent, border-input) with
  motion-safe: utilities for prefers-reduced-motion compliance

Tests (app/components/__tests__/SearchInput.test.tsx)
~50 focused cases covering ARIA wiring, all keyboard paths, click/hover,
clear button, loading/empty states, controlled vs uncontrolled, maxSuggestions,
disabled, live region, outside-click close, and a snapshot.

Docs: docs/SEARCH_INPUT.md — props table, keyboard table, usage examples,
comparison with the navbar-specific SearchInput.

Closes Predictify-org#477
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

@NnamdiCyber is attempting to deploy a commit to the Jagadeesh B's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 23, 2026

Copy link
Copy Markdown

@NnamdiCyber Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add accessible search input with combobox

1 participant