dnd kit Mobile: Touch Drag That Lets the Page Scroll
You built a sortable list or a kanban board with dnd kit, it works with a mouse, and on a phone it falls apart: the page scrolls while the card stays put, or the card grabs every swipe and the list never scrolls. This tutorial fixes dnd kit mobile drag step by step: which sensors to register, the activation delay versus scroll trade-off, where touch-action goes, why iOS Safari cancels drags, and the keyboard and tap paths that make drag and drop accessible. The sensor setup is the one in the Wingo UI Kanban, which the Deal Pipeline builds on; the List shows the drag handle variant.
As of October 2026, the latest @dnd-kit/core is still 6.3.1 from December 2024, and the legacy docs (opens in a new tab) recommend the new version of dnd kit (@dnd-kit/react for React), which is at 0.5.0, before 1.0. This post targets @dnd-kit/core 6.3.1 and @dnd-kit/sortable 10.0.0.
Why is dnd kit not working on mobile?
Because DndContext uses the PointerSensor and the KeyboardSensor by default, and pointer events cannot stop a scroll. When a finger moves on an element that allows panning, the browser starts scrolling and fires pointercancel, which ends the drag, and calling preventDefault() in pointermove changes nothing. The dnd kit Pointer sensor docs (opens in a new tab) say it plainly: "Using touch-action: none; is the only way to reliably prevent scrolling for pointer events."
That leaves two tools, each with a cost:
touch-action: noneon the draggable. The drag works, and a swipe that starts on that element can never scroll the page. Fine for a small grip, bad for a list of full-width rows: when every row isnone, no swipe on the list scrolls it. The docs suggestnoneon a drag handle only, for this reason.- The TouchSensor. Touch events can be canceled in
touchmove, so a drag can wait for a long press and still let a swipe scroll. The same docs page recommends the Mouse and Touch sensors whenevernonedoes not fit your layout.
The problem is old: issue #435 (opens in a new tab), "Dragging with PointerSensor does not work well on touch devices", dates from August 2021. When the rows themselves must scroll, React drag and drop on touch screen devices needs the TouchSensor, next to a sensor for the mouse.
Which dnd kit sensors work on a touch screen?
Three: a pointer sensor that ignores fingers, the dnd kit touch sensor with a delay, and the keyboard sensor. The trap is registering the stock PointerSensor next to the TouchSensor. A touch fires pointerdown before touchstart, and DndContext ignores a sensor's activator while another sensor is pending; the check in the 6.3.1 source is commented "Another sensor is already instantiating". So the PointerSensor takes every finger and the TouchSensor's delay never runs.
We tested it with @dnd-kit/core 6.3.1 in headless Chromium with emulated touch: one draggable on a page taller than the screen, an 8px distance on the pointer sensor, 250ms and 8px on the touch sensor, and a finger moving 60px either at once or after a 400ms hold.
A common workaround picks one sensor by device type, which breaks on touch screen laptops, where one person uses both the trackpad and the screen. The last row is the cleaner fix: a PointerSensor subclass whose activator skips touch pointers, so the mouse and pen take the pointer path and fingers take the touch path. Our Kanban runs the same pointer and touch sensors. Save this as components/use-drag-sensors.ts; the full example at the end imports it:
The stock MouseSensor plus TouchSensor pairing from the docs works too.
What delay and tolerance should the TouchSensor use?
250ms with 5 to 8px of tolerance. The delay is how long a finger must rest before the card lifts; the tolerance is how far it may drift in that time before dnd kit gives up and lets the browser scroll. Those two numbers are the whole dnd kit mobile scroll trade-off:
Never use a tolerance of zero. The Touch sensor docs (opens in a new tab) say that with zero, any movement during the delay aborts the drag, and a finger resting on glass always moves a little. Issue #1955 (opens in a new tab), opened in March 2026 against the rewrite, reported drags that never started on some Android phones, mostly Samsung; in July 2026 the reporter traced it to a tolerance: 0 and fixed it with 5. The rewrite's PointerSensor gives touch a 250ms delay with 5px tolerance (opens in a new tab) by default; our Kanban and Deal Pipeline use 250ms and 8px.
A long press needs feedback at the moment it fires, or people let go too early. The Kanban calls navigator.vibrate(10) in onDragStart when the activator event is a TouchEvent, and lifts the card with a small scale and a 2 degree tilt on a spring. The tick does nothing on an iPhone, because Safari on iOS has no Vibration API (caniuse (opens in a new tab), checked in October 2026; the web vibration API guide covers what an iPhone can do instead).
Where does touch-action go?
On the element that receives the listeners, with a value that depends on which sensor owns the finger. The docs recommend manipulation for the TouchSensor and none for the PointerSensor, and the browser reads the value when the touch starts: changing it after pointerdown or touchstart is ignored. Put it in the CSS, never in a drag start handler.
manipulation keeps panning and pinch zoom and drops double-tap zoom; once the drag is active, the TouchSensor cancels touchmove itself. A long-press surface needs two more classes: select-none, so the press does not select the card's text, and [-webkit-touch-callout:none], so iOS does not open its callout. dnd kit already cancels contextmenu on the window while a touch is pending or dragging.
The Wingo UI List takes the grip route: a 32px button with touch-none whose hit area grows to 44px on coarse pointers (why 44px), so a swipe on the row still scrolls the page or opens its swipe actions.
Why does a drag cancel on iPhone when the page scrolls?
Because @dnd-kit/core cancels any pointer or touch drag on a window resize, and Safari on iOS fires resize when its toolbar collapses or expands as the document scrolls. Issue #686 (opens in a new tab) traced it on iOS 15 in March 2022. It was closed in February 2026 as filed against the old version, not fixed: the resize listener is still in the 6.3.1 sensors, so the cancel still happens.
The fix is layout: keep the drag inside its own scroll container so the document does not move. Give the board or list a real height (h-dvh, or flex-1 min-h-0 in a full-height layout; see 100vh on mobile), overflow-y-auto for a list or overflow-x-auto for a board, and overscroll-contain so reaching the end does not hand the scroll to the page.
The container also decides what auto-scroll moves. dnd kit scrolls the item's scrollable ancestors, meaning elements with overflow set to auto or scroll, plus the document, when the pointer comes within 20% of their edge. A wrapper with overflow: hidden does not count, so a board clipped that way cannot auto-scroll to its hidden columns; the page scrolls instead, if it can. If a phone board uses scroll snap, pause it during the drag: the Kanban board is snap-x snap-mandatory with data-dragging:snap-none, so the auto-scroll can glide to the next column.
Try it at phone width (or in device mode): the Kanban shows one column at a time with the next one peeking, a swipe scrolls it, and a 250ms press lifts a card you can carry to the edge to reach the next column.
$ npx wingo-ui@latest add kanbanHow do you make drag and drop accessible?
With two separate paths, and dnd kit gives you one of them. The KeyboardSensor covers keyboard users: by default Space or Enter lifts, the arrow keys move, Space, Enter or Tab drops and Escape cancels, while DndContext reads instructions and live announcements to screen readers. Write your own announcements, because the defaults read raw ids ("Picked up draggable item d02."). The Kanban passes keyboardCodes: { start: ["Space"], cancel: ["Escape"], end: ["Space", "Enter"] }, since Enter opens a card, and its own coordinateGetter sends Left and Right to the nearest card in the next column that accepts it.
The second path is for people who can tap but not drag, because of a tremor, a trackball or a head pointer. WCAG 2.2 success criterion 2.5.7 Dragging Movements (opens in a new tab) (level AA) requires that dragging "can be achieved by a single pointer without dragging", and the W3C's explanation says keyboard equivalence alone does not meet it. One of its examples is the pattern a list needs: after a tap on an item, controls next to it move it up or down. For a board, the same idea is a menu that names the target column. The Kanban puts Move to first in every card's ⋯ menu, which opens as a bottom sheet on phones.
How do you build a sortable list that works on phones?
Combine everything above in one component: the useDragSensors hook, long-press rows, a list with its own scroller, named announcements and up and down buttons for the tap path. It also passes id={useId()} to DndContext. Without it, dnd kit numbers its aria-describedby ids with a module-level counter, the server and the browser can count differently, and React reports a hydration mismatch in Next.js.
Install the packages with npm i @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities lucide-react and the free Button with npx wingo-ui@latest add button, then save this as components/sortable-tasks.tsx, next to use-drag-sensors.ts:
A server page, such as app/tasks/page.tsx, can render it with plain data:
Then test on a real phone: device mode does not show the Safari toolbar, a real finger's drift or the system long press. Try every path with a screen reader on, too.
If you would rather not own this code for a plain list, the List has sortable rows built in: turn on sortable, then drag a row by its grip, or Tab to a grip and use Space and the arrow keys; Alt with Up or Down moves a focused row one step. It has no tap path for reordering, so for 2.5.7 add Move up and Move down entries to each row's actions menu (its ⋯ button is always visible on touch) and reorder your items in their handlers.
$ npx wingo-ui@latest add listIs there a React kanban board that already works on phones?
Yes: the Wingo UI Kanban ships the touch, mouse and keyboard setup from this post, the Move to menu and the phone layout. npx wingo-ui@latest add kanban copies it and everything it imports into your project and installs its npm packages. The Kanban, List and Deal Pipeline are Pro components (pricing). For the other phone gestures, such as swipe actions and drag to dismiss, read the mobile-first React components guide. The React form components guide applies the same touch, keyboard and screen reader rules to every form control, and more tutorials like this one are in the component guides category.
FAQ
Why is dnd kit not working on mobile?
DndContext uses the PointerSensor by default, and pointer events cannot stop the browser from scrolling, so a touch drag is canceled as soon as the page starts to pan. Either set touch-action: none on a drag handle, or let a TouchSensor with a 250ms delay own the finger, next to a pointer sensor that skips touch, so a long press lifts the item and a swipe still scrolls.
Can I use PointerSensor and TouchSensor together in dnd kit?
Not the stock PointerSensor. A touch fires pointerdown before touchstart, so the PointerSensor claims the finger and the TouchSensor's delay never runs; subclass PointerSensor with an activator that skips pointerType touch, or pair MouseSensor with TouchSensor.
What delay and tolerance should the dnd kit TouchSensor use?
Start with a 250ms delay and 5 to 8px of tolerance; the dnd kit rewrite uses 250ms and 5px as its touch default. Never set the tolerance to 0, because the docs say any movement during the delay then aborts the drag, and a resting finger always drifts a little.
Why does dnd kit cancel my drag on iPhone?
@dnd-kit/core cancels pointer and touch drags on a window resize, and Safari on iOS fires resize when its toolbar collapses or expands as the page scrolls. Keep the drag inside its own scroll container so the document never scrolls during a drag.
Is keyboard drag and drop enough for accessibility?
It covers keyboard users but not WCAG 2.2 success criterion 2.5.7 Dragging Movements, which asks for a way to do the same thing with single taps or clicks. Add a Move to menu or up and down buttons next to the drag.
Should I switch to @dnd-kit/react for mobile drag?
Not for touch alone. The legacy docs point to the new version, and its PointerSensor gives touch a 250ms delay with 5px tolerance by default, but as of October 2026 @dnd-kit/react is at 0.5.0, before 1.0, with a different API. On @dnd-kit/core 6.3.1, the sensor setup in this post fixes touch drag.
- dnd kit
- Drag and drop
- Mobile UI
- Accessibility
- React