Skip to content

feat/custom-select-dropdowns#624

Open
haseeblodhi1899 wants to merge 6 commits into
rmyndharis:mainfrom
haseeblodhi1899:feat/custom-select-dropdowns
Open

feat/custom-select-dropdowns#624
haseeblodhi1899 wants to merge 6 commits into
rmyndharis:mainfrom
haseeblodhi1899:feat/custom-select-dropdowns

Conversation

@haseeblodhi1899

@haseeblodhi1899 haseeblodhi1899 commented Jul 4, 2026

Copy link
Copy Markdown

Description

Replaces native HTML <select> elements in the dashboard with a reusable
CustomSelect component that matches the existing design system (colors,
typography, spacing, border radius, animations, dark/light themes).

Changes

  • New: CustomSelect component with keyboard navigation, ARIA
    accessibility, theme support, responsive design, RTL support
  • Sessions: "All Status" filter → CustomSelect
  • Logs: "All Severities" filter → CustomSelect
  • Login: Language picker → CustomSelect
  • No changes to sidebar (already uses custom menus)

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactor (UI consistency, no breaking changes)

Checklist

  • Tests added/updated
  • Documentation updated
  • Lint passes (0 errors, 2 pre-existing warnings)
  • Build passes (tsc + vite)
  • Self-reviewed

Screenshots (if applicable)

image image image

Related Issues

Closes #

Replace native HTML <select> elements with a custom dropdown component
that matches the dashboard design system (colors, typography, spacing,
border radius, theme support, RTL, responsive).

Includes full keyboard navigation, ARIA attributes, and outside-click
dismissal.
Use the new CustomSelect component for the 'All Status' (Sessions)
and 'All Severities' (Logs) dropdowns.
Use the CustomSelect component for the language picker for UI consistency.

@rmyndharis rmyndharis left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the component matches the design system nicely and I verified the branch locally (tsc + vite build, lint, and i18n:check all pass). One accessibility regression needs fixing before merge, since this replaces a native <select>:

Focus is dropped to <body> on every selection and on Escape.

While the dropdown is open, real DOM focus is moved onto an option <button> via the effect:

useEffect(() => {
  if (isOpen && focusedIndex >= 0 && listRef.current) {
    const items = listRef.current.querySelectorAll<HTMLButtonElement>('.custom-select-option');
    items[focusedIndex]?.focus();
  }
}, [isOpen, focusedIndex]);

On close() (selecting an option, Escape, or Tab) isOpen becomes false, which unmounts the entire {isOpen && (...)} dropdown subtree — including the option button that currently holds focus. Nothing returns focus to the trigger (there's no triggerRef), so the browser resets focus to document.body.

Impact: a keyboard/screen-reader user opens the filter, arrows to an option, presses Enter — and is silently dumped to the top of the page. The next Tab starts from the first focusable element and the screen reader loses its reading position. A native <select> returns focus to the control after selection/Escape; this is a WCAG 2.4.3 (Focus Order) regression, and it happens on all three call sites (Sessions, Logs, Login) on every selection.

Fix (~5 lines): add a ref to the trigger button and call triggerRef.current?.focus() inside close().

Non-blocking nice-to-haves:

  • Restore native type-ahead (first-letter jump) and Home/End key support — the native <select> this replaces had them.
  • The trigger sets aria-activedescendant while real DOM focus roves to the option buttons. Those are two different, mutually-exclusive ARIA patterns — pick the roving-focus one you already use and drop aria-activedescendant when you touch this.

Once focus returns to the trigger on close, this is good to go.

Adds a triggerRef and calls triggerRef.current?.focus() inside close()
so focus returns to the trigger button when an option is selected or
Escape is pressed. Without this, unmounting the dropdown drops focus
to document.body (WCAG 2.4.3 regression).

Also removes aria-activedescendant (mutually exclusive with the
roving-focus pattern used), and adds Home/End and type-ahead
(first-letter jump) support to restore native <select> behavior.
@haseeblodhi1899

Copy link
Copy Markdown
Author

Thanks for the thorough review!

I added a triggerRef and called .focus() on it inside close(), so focus returns to the trigger button instead of falling back to <body> on close.

Also addressed both suggestions:

  • Dropped aria-activedescendant since real DOM focus is already the source of truth
  • Added type-ahead (first-letter jump) plus Home/End support to match native <select> behavior

Also resolved the CHANGELOG conflict.

@haseeblodhi1899 haseeblodhi1899 changed the title Feat/custom select dropdowns feat/custom-select-dropdowns Jul 5, 2026
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.

2 participants