#2136·dnd-kit

[Svelte] DragOverlay disappears one frame before the hidden source is restored

Author: IdrisCelikCreated Sep 12, 2026Updated Sep 12, 2026

When the source content is hidden using draggable.isDragSource, the default drop animation ends with a brief blank frame: the overlay disappears before the source becomes visible again.

The source element is already in the DOM and remains connected throughout the animation and cleanup. This reproduces with a single draggable, without Query, network requests, list updates, or application state changes.

Environment

  • @dnd-kit/svelte: 0.5.0
  • @dnd-kit/dom: 0.5.0
  • Svelte: 5.57.0
  • Chromium: 151.0.7922.34, via Playwright 1.62.1
  • Windows
  • Default drop animation; normal motion preference

Minimal reproduction

In a Svelte/Vite project with the versions above, use these two components. The original card keeps its dimensions while its content is hidden during the drag.

App.svelte

svelte
<script>
  import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
  import Card from './Card.svelte';
</script>

<DragDropProvider>
  <main style="padding: 80px; font-family: sans-serif;">
    <p>Drag the card away, release, and watch the end of its return animation.</p>
    <Card />
  </main>
  <DragOverlay>
    <div style="box-sizing: border-box; width: 200px; padding: 24px; border: 2px solid #2563eb; background: #dbeafe; font-family: sans-serif;">Drag me</div>
  </DragOverlay>
</DragDropProvider>

Card.svelte

svelte
<script>
  import { createDraggable } from '@dnd-kit/svelte';
  const draggable = createDraggable({ id: 'card' });
</script>

<div style="width: 200px; user-select: none; touch-action: none;" {@attach draggable.attach}>
  <div
    data-card
    style="box-sizing: border-box; width: 200px; padding: 24px; border: 2px solid #2563eb; background: #dbeafe;"
    style:visibility={draggable.isDragSource ? 'hidden' : 'visible'}
  >Drag me</div>
</div>
  1. Open the reproduction in Chromium.
  2. Set CPU throttling to 4× slowdown to make the handoff easier to inspect.
  3. Drag the card several hundred pixels away from its original position.
  4. Release it and watch the end of the return animation.

The problem also reproduced without CPU throttling.

Expected behavior

The overlay remains visible until the source is visible again. No frame during the handoff should have both hidden.

Actual behavior

The overlay animates back correctly, disappears, and then the source becomes visible on a later frame.

A requestAnimationFrame observer sampled both elements' visibility until cleanup completed. The same source DOM node remained connected in every sample:

CPU throttling Existing timer cleanup: blank frame samples Microtask cleanup: blank frame samples
None 1 0
4× slowdown 1 0

These are observations from the isolated reproduction, not a claim that every device will always show exactly one blank frame. A DOM assertion that only checks the final settled state misses this intermediate state.

Cause

In Feedback.ts, cleanup hides the overlay by removing its dragging attribute and resetting its styles. For an overlay, finalize then runs in a separate timer task.

finalize sets the source status to idle, allowing the drag operation to reset and Svelte to reveal the source. The task boundary permits rendering between hiding the overlay and revealing the source.

Proposed fix

Keep finalization asynchronous, but schedule it in a microtask:

diff
 if (feedbackElement === this.overlay) {
-  setTimeout(finalize, 0);
+  queueMicrotask(finalize);
 } else {
   finalize();
 }

In the Svelte reproduction this lets the operation reset and the resulting DOM update finish before the next paint. The change is in the shared DOM feedback plugin; it does not require an application-level animation workaround.

Validation

  • The standalone reproduction has no Query dependency or application data layer. The existing timer produced a blank frame at both 1× and 4×; replacing only that scheduling call removed it.
  • Additional browser checks in a calendar application passed for planner → todo, todo → planner, and day → day at 4× slowdown.
  • Reduced-motion drops, keyboard cancellation with focus restoration, a second drag after settling, and rollback after a rejected optimistic update also passed.

The patch has been validated with the Svelte adapter in Chromium. Other adapters and browsers have not been validated here. A regression test should inspect visibility across animation frames through the final overlay-to-source handoff, rather than only checking the final DOM.