Repository navigation
Select multiple: Space requires 2+ presses to toggle items (double-call in keydown) #2049
Description
Describe the bug
In Select.Root with type="multiple", pressing Space on a highlighted item does not reliably toggle selection on the first press. The trigger's keydown handler calls handleKeyboardSelection() twice within a single Space press — once in the early SPACE-selection branch and once in the typeahead-fallback branch — yielding a net no-op (toggle on → toggle off) for multi-select.
Single-select is unaffected because it returns early after the first call.
Steps to reproduce
This issue can be reproduced in the example in the docs.
- Render a
<Select.Root type="multiple">with at least two items whosetextContentcontains spaces, e.g."Chocolate","Cherry". - Tab focus to the trigger.
- Press Enter or Space to open the dropdown.
- Press ArrowDown to highlight an item.
- Press Space once.
Expected
The highlighted item toggles selected/deselected on the first Space press.
Actual
Nothing visibly happens. A second (sometimes third) Space press is needed before a toggle "sticks". Click and Enter both work correctly — only Space is affected.
Root cause
In SelectTriggerState.onkeydown (src/lib/bits/select/select.svelte.ts, around the onkeydown for the trigger state), Space falls through two handler branches:
Branch 1 — early SPACE-selection:
if ((e.key === kbd.ENTER || (e.key === kbd.SPACE && this.#domTypeahead.search === "")) && !e.isComposing) {
e.preventDefault();
const shouldReturn = this.#handleKeyboardSelection();
if (shouldReturn) return;
}For multi-select, handleKeyboardSelection() returns false (no auto-close), so execution falls through.
Branch 2 — typeahead fallback (further down the same function):
if (!isModifierKey && (isCharacterKey || isSpaceKey)) {
const matchedNode = this.#domTypeahead.handleTypeaheadSearch(e.key, candidateNodes);
if (!matchedNode && isSpaceKey) {
e.preventDefault();
this.#handleKeyboardSelection();
}
return;
}handleTypeaheadSearch(" ", …) appends " " to the search buffer. getNextMatch(values, " ", currentMatch) returns undefined (no item starts with " "), so !matchedNode && isSpaceKey evaluates true and handleKeyboardSelection() is called again, reverting the toggle from Branch 1.
Single-select isn't hit because Branch 1 returns true from handleKeyboardSelection (auto-close path), short-circuiting before Branch 2.
Suggested fix
After Branch 1 fires for SPACE in multi-select, return so Branch 2 doesn't run. Either:
if ((e.key === ENTER || (e.key === SPACE && this.#domTypeahead.search === "")) && !e.isComposing) {
e.preventDefault();
const shouldReturn = this.#handleKeyboardSelection();
if (shouldReturn) return;
if (e.key === SPACE) return; // ← also return after multi-select SPACE selection
}Or track a handledBySpace flag and skip the typeahead branch when set. Happy to open a PR.
Workaround
Intercept Space in capture phase on the trigger, toggle the value directly, and stopImmediatePropagation to block the buggy handler. (Verified working in our codebase.)
Reproduction
https://stackblitz.com/run?file=src%2Froutes%2F%2Bpage.svelte&terminalHeight=10
Logs
System Info
System:
OS: Windows 10 10.0.19045
CPU: (16) x64 Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz
Memory: 2.74 GB / 15.85 GB
Binaries:
Node: 24.4.0 - C:\nvm4w\nodejs\node.EXE
npm: 11.12.0 - C:\nvm4w\nodejs\npm.CMD
pnpm: 10.13.1 - C:\Users\sarak\AppData\Local\pnpm\pnpm.EXE
bun: 1.3.13 - C:\Users\sarak\.bun\bin\bun.EXE
Browsers:
Chrome: 147.0.7727.138
npmPackages:
@sveltejs/kit: ^2.57.0 => 2.57.1
bits-ui: ^2.17.3 => 2.17.3
svelte: ^5.55.2 => 5.55.3Severity
annoyance
Activity