KeyboardSensor: event.preventDefault() in onDragMove also disables keyboard scrolling
- Dominant language
- TypeScript
- Stars
- 17.6k
- Forks
- 924
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 2
Description
## Description
When implementing custom keyboard movement logic in a sortable UI, calling `event.preventDefault()` inside `onDragMove` successfully suppresses the default keyboard movement step, but it also disables keyboard scrolling in scrollable containers.
This makes it impossible to override keyboard movement behavior while still keeping the drag target visible inside an `overflow: auto` container.
From the consumer side, one flag currently controls two unrelated behaviors:
| Behavior | Desired? | Result |
|---|---|---|
| Prevent default keyboard movement step | Yes | Works |
| Prevent built-in keyboard scrolling | No | Also happens |
## Context
We are using the React API built around `DragDropProvider` / `useSortable`, which is the current React-specific dnd-kit API.
The issue appears specifically during keyboard dragging in a scrollable container. According to the docs, keyboard dragging uses its own scrolling behavior rather than the generic pointer-based auto-scroll path.
## Steps to reproduce
1. Put sortable items inside a fixed-height scroll container with `overflow: auto`
2. Start keyboard dragging on one item
3. In `onDragMove`, call `event.preventDefault()` for keyboard events in order to replace the default movement logic with custom logic
4. Press ArrowDown repeatedly until the drag target moves beyond the visible area
## Expected behavior
Calling `event.preventDefault()` to suppress the default keyboard movement step should **not** also disable scrolling that keeps the keyboard drag target visible.
The scroll container should continue following the keyboard drag when the target moves outside the visible area.
## Actual behavior
The scroll container does not scroll.
The drag target continues to move logically, but once it goes beyond the visible area, it disappears off-screen.
If `event.preventDefault()` is removed, scrolling works again, but then the default keyboard movement step is active too, so consumers cannot override one behavior without losing the other.
## Why this seems to happen
The keyboard scrolling path appears to be skipped when `event.defaultPrevented` is already true.
In practice, this means:
- consumers must call `event.preventDefault()` to stop the default movement step
- but doing so also disables built-in keyboard scrolling
- manual scrolling via `scrollBy()` becomes a fragile workaround and causes visible jitter
## Attempted workaround
We tried manually calling `scrollParent.scrollBy({ behavior: 'instant' })` before computing the custom snapped/swapped position.
This led to visible jitter because the externally applied scroll offset did not stay in sync with the internal drag position used for rendering.
## Minimal reproduction
StackBlitz reproduction:
https://stackblitz.com/edit/vitejs-vite-wgx1q23d?file=src%2FApp.tsx
To verify the behavior:
1. Focus an item with `Tab`
2. Press `Space` to start keyboard dragging
3. Press `ArrowDown` repeatedly until the drag target moves beyond the visible area
4. Observe that the container does not scroll when `event.preventDefault()` is used inside `onDragMove`
If the `event.preventDefault()` line is removed, scrolling works again, but the default keyboard movement step is active too.
## Suggested fix
It would help if preventing the default keyboard movement step did not also disable keyboard scrolling.
Possible directions:
1. Do not treat `defaultPrevented` as a reason to skip keyboard scrolling
2. Or introduce a more granular API so consumers can:
- suppress the default keyboard position update
- while still preserving built-in keyboard scrolling
## Environment
- `@dnd-kit/react`: 0.3.2
- `@dnd-kit/dom`: 0.3.2
- React: 18
- Browser: Chrome 135+
- OS: macOS
Contributor guide
Assessment
This issue has not been assessed yet.