Skip to content

md-select: Mouse-click selection leaves stale roving tabindex, causing typeahead to revert selection on next keypress #5913

@kaseyhinton

Description

@kaseyhinton

What is affected?

Component

Description

When an option is selected via mouse click in md-outlined-select or md-filled-select, pressing any printable key while the select is closed and focused causes the selection to change unexpectedly. This does not occur when the option is selected via keyboard (arrow keys + Enter).

The root cause is a tabindex desync between the mouse and keyboard selection paths. When the dropdown is open, the ListController manages roving tabindex — keyboard navigation calls deactivateItem (tabIndex = -1) and activateItem (tabIndex = 0). Keyboard selection goes through this path, so after closing, the correct option has tabIndex = 0.

Mouse-click selection bypasses the ListController. The click fires a close-menu event with reason.kind === 'click-selection', and handleCloseMenu calls selectItem(), which correctly updates .selected and the internal VALUE/displayText — but never updates tabIndex on the menu items. The previously selected option retains tabIndex = 0.

When a printable key is subsequently pressed, handleKeydown delegates to the TypeaheadController. In beginTypeahead(), the controller locates the "active" item by finding the item with tabIndex === 0 — which is the old selection. The typeahead then searches from that stale position and selects whichever option matches the keystroke, making the selection appear to revert.

Reproduction

code pen

Steps to reproduce:

  1. Render an md-outlined-select (or md-filled-select) with multiple options (e.g., "Apple", "Banana", "Orange")
  2. "Apple" is the initial selection
  3. Click the select to open the dropdown
  4. Click "Orange" with the mouse — the select now displays "Orange"
  5. Without clicking elsewhere, press the a key

Expected: The select remains on "Orange" (or performs typeahead from "Orange" as the active baseline).

Actual: The select reverts to "Apple" because the typeahead controller found "Apple" via its stale tabIndex = 0 and matched the a keystroke to it.

Note: If step 4 is done with the keyboard (arrow to "Orange", press Enter), pressing a in step 5 does not revert the selection.

Workaround

select.addEventListener('change', () => {
  for (const option of select.options) {
    option.tabIndex = option.selected ? 0 : -1;
  }
});

Is this a regression?

No or unsure. This never worked, or I haven't tried before.

Affected versions

Fails in 2.4.1

Browser/OS/Node environment

@material/web version: 2.4.1
Browsers: Reproducible in Chrome, Edge, Firefox (all current stable)
Also reproducible on the official Material Web documentation/demo page

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions