Draggable
Drag and drop for pointer input, with drop targets, previews, sorting, and auto-scrolling.
Usage guidelines
- Dragging is a pointer enhancement: Draggable responds to mouse, touch, and pen input only. Every action that can be done by dragging must also be possible with a keyboard, a click, or a tap.
- Provide the alternative in your application: A “Move to” menu, move buttons, or a shortcut on the focused item such as Alt+← and Alt+→ all work. Have them call the same function as the drop handler so both paths apply the same rules. After a keyboard move, keep focus on the moved item and announce the result in a live region. The sortable list demos show this.
- Drag handles are not controls:
<Draggable.Handle>isn’t focusable and has no keyboard behavior. Mark itaria-hidden="true"when it only contains a decorative icon, and don’t rely on it as the keyboard entry point. - Drags stay within the page: Draggable tracks pointer events rather than the browser’s native drag and drop. It can’t move data between browser windows or receive files from the operating system. See Native file drops.
- Use it for spatial interactions: Reordering lists, moving items between regions, and positioning items on a canvas are good fits. To pick a value from a list, use a component such as Select instead.
Anatomy
Import the component and assemble its parts:
<Draggable.Root> is an element that can be picked up, and <Draggable.Target> is a place where it can be dropped. Both need a <Draggable.Provider> above them. The provider renders no element of its own.
The remaining parts are optional:
<Draggable.Handle>restricts where a drag can start. See Drag handle.<Draggable.Preview>customizes what follows the pointer. See Drag preview.<Draggable.CollisionProvider>groups items that can be reordered. See Sorting a list.<Draggable.Viewport>marks a scroll container that should auto-scroll during a drag. See Auto-scrolling.
One shared manager handles every drag on the page, so a <Draggable.Provider> doesn’t isolate its contents from other providers. It does two things. It defines the default kind for the parts inside it, and it gives custom previews access to React context, so place it inside any context providers a custom preview needs to read.
Kinds and payloads
A kind identifies a type of draggable item. Create one with Draggable.createKind, pass it to the kind prop of a <Draggable.Root>, and list it in the accept prop of the targets that take it. A target ignores drags of any other kind.
The type argument of createKind declares the item’s payload. The payload is any value you attach to a draggable, and it’s available as source.payload in every drop target and event handler that accepts the kind.
A kind declared with a payload type requires the payload prop. Omit the type argument when the kind alone identifies the item:
Base UI tracks the payload by identity. Prefer a primitive such as an ID, as above. When the payload has to be an object, memoize it so that unrelated re-renders don’t replace it. A new object during a drag re-notifies useActiveDrag subscribers and discards any updatePayload call:
Each createKind call creates a unique identity, so two calls with the same name don’t match. Declare each kind once, usually at module scope, and share the same value between the sources and targets of an interaction. The name is only a debugging aid.
When kind and accept are both omitted, a source and a target match as long as they’re inside the same <Draggable.Provider>. This is enough for a single self-contained interaction, like the demo at the top of this page.
Matching several kinds
A target, viewport, or monitor can accept an array of kinds. source.payload is then a union of their payload types. Use a kind’s matches method to narrow it:
Use Draggable.anyKind to accept every drag, for example on a trash zone. Its source.payload is unknown until narrowed with matches.
Drag events
Drag sources and drop targets both fire event handlers as a drag progresses. Every handler receives an event object first, and an eventDetails object second with the event reason and the native event.
When extracting a handler, use the types on its component namespace, such as Draggable.Root.MoveStartEvent<Payload>, Draggable.Root.MoveStartEventDetails, and Draggable.Root.MoveStartEventReason. Target handlers expose corresponding types such as Draggable.Target.DropEvent<SourcePayload, TargetPayload> and Draggable.Target.DropEventDetails. Event types also accept drag-data type arguments when needed. The existing standalone event types remain available for shared handlers and imperative registrations.
Drag source events
<Draggable.Root> fires the handlers below, in this order:
Commit a successful drop in onMoveEnd when eventDetails.reason is 'drop'. A release outside any target also ends with event.canceled set to false, so that flag alone doesn’t identify a drop. When the handler also clears temporary state, use try/finally so the cleanup runs even if committing throws:
Every event carries the drag source and a location history:
location.currentis the pointer position and the drop targets under it, innermost first, for this event.location.previousis the same information at the previous event. Compare it withlocation.currentto detect what changed.location.initialis where the drag began.location.grabOffsetis the pointer’s offset from the source’s top-left corner at pickup.
A drag keeps going if the source element leaves the DOM, for example when a virtualized list unmounts the dragged row. Identify the dragged item with source.payload rather than its element. To stop a drag from code, for example when the dragged record is deleted, call cancelDrag.
To observe every drag on the page rather than one source, use useDragMonitor.
Drop target events
<Draggable.Target> fires the handlers below. They receive the drag source, the target’s own record as target, and the same location history:
onDraggableStart fires only when this target is already under the pointer when the drag starts. To observe every drop regardless of which target received it, use the source’s or a monitor’s onMoveEnd and check for the 'drop' reason. When a drag ends on a target, the source’s onMoveEnd runs first, then the target’s onDraggableDrop, then the monitors’ onMoveEnd.
Use onDraggableEnter and onDraggableLeave together to react to a pause over a target. For example, expand a collapsed group after the drag hovers it for a moment:
Drop targets
<Draggable.Target> marks where a drag can be released. Pass the kinds it takes to accept, and handle the drop in onDraggableDrop:
Targets can be nested. The innermost target under the pointer that accepts the drag receives the drop. See Drop target events for the full set of handlers.
Accepting drops
accept decides by kind. For rules that depend on the item being dragged or on the target’s current state, add canDrop. Base UI calls it every time it looks for a target under the pointer. Return false to skip this target and let an ancestor receive the drop, or 'reject' to block the drop on this target and everything inside it:
Use disabled to turn a target off entirely. See Nested drop targets for how these options interact.
Identifying a target
Most targets don’t need extra data. A target rendered for each row or column can use that value directly in its handlers:
Pass payload when other code must identify the target, for instance a monitor reading location.current.dropTargets. The value is available as target.payload in the target’s own handlers:
A target can also declare its own kind. This lets a shared handler tell several types of target apart with matches, and types target.payload at the same time:
Auto-scrolling
Scroll containers don’t scroll during a drag unless you opt them in. Render <Draggable.Viewport> on each container that should scroll, including nested ones. The board below registers both lists, and slows down the second one.
Drag the card into either list, at the slot you want. Both lists scroll near their edges; the second list scrolls more slowly.
A viewport scrolls while the pointer is near one of its edges and more content remains in that direction. It scrolls on the axes whose overflow is auto or scroll. Base UI looks for drop targets again as the content scrolls, so a target that slides under a stationary pointer can receive the drop.
Nesting follows the DOM tree. The innermost viewport scrolls first, and an ancestor viewport scrolls on the axes the inner one doesn’t use or has exhausted. A vertical column inside a horizontal board scrolls each on its own axis.
Pass disabled to pause a viewport, for example while a list is filtered. Pass accept to scroll only for some kinds. To change the speed, restrict the direction, or scroll something that isn’t a scroll container, see the examples.
Scrolling the page
The page doesn’t scroll during a drag by default. Since <Draggable.Viewport> renders an element, register the document with useDragDropManager instead. It accepts the same options as the viewport part:
Inner viewports take precedence on the axes they use. overflow: hidden on <html> or <body> prevents page scrolling on that axis, which includes the scroll lock of a modal Dialog.
Testing
A drag is a sequence of pointer events, so it can be driven from a test. Most of what’s worth testing isn’t the gesture, though. The drop handler and the keyboard alternative call the same application function, so test that function, or the keyboard path with ordinary Testing Library interactions, and keep a few gesture tests for the wiring between the parts.
Choosing an environment
jsdom has no layout and no document.elementFromPoint, so Base UI can’t find a drop target there. A drag in jsdom starts, moves, and ends normally, but always with the 'outside-release' reason. Use jsdom to test activation, disabled and onBeforeMoveStart, the lifecycle handlers, data-dragging, and the preview. Use a real browser, through Vitest browser mode or an end-to-end tool, to test drops, drag-over styling, sorting, and auto-scrolling.
Simulating a drag
Use the pointer API of @testing-library/user-event. Press on the source, move past the activation threshold, then release. Native HTML5 drag events such as dragstart don’t start a Base UI drag.
Touch starts a drag after a 250ms hold, so advance fake timers between the press and the move. Dispatch the events directly in that case, with the same pointerId throughout and the moves on document:
Waiting and asserting
onMoveStart and onMoveEnd fire synchronously with the event that causes them. Movement and drag-over updates run once per animation frame, so wait for them:
Assert on what a user could observe: the data attributes on the source and targets, and the arguments your handlers received. Handlers take two arguments, so check mock.calls[0][0] for the event and mock.calls[0][1] for the details rather than toHaveBeenCalledWith with a single argument. A successful drop has the 'drop' reason:
Cleaning up
Every drag on the page goes through one manager, so a drag left running leaks into the next test. End every drag you start, with a release or Esc, and do it in afterEach when a test asserts mid-drag. Restore real timers after using fake ones.
Examples
Drag handle
By default, the whole element starts a drag. Render <Draggable.Handle> inside the root to start drags from that element only, so the rest of the item stays interactive. A draggable uses its first handle and ignores any other.
The handle renders a <span> and isn’t focusable. Keep the item operable through its own controls or shortcuts, as described in the usage guidelines. The dashboard demos on this page move the focused widget with Alt+← and Alt+→.
Disabling a drag
Pass disabled to <Draggable.Root> to prevent an item from being picked up. Clicks and context menus on the element keep working.
When the decision depends on the gesture itself, for example on which handle was pressed or on a modifier key, use onBeforeMoveStart instead. It fires just before a drag starts. Call eventDetails.cancel() to prevent it:
Storing data during a drag
Both source and target records let handlers store data for later handlers to read. Declare its type as the second type argument of createKind, and set it with updateDragData. It starts as undefined on each pickup. Initialize source data in onBeforeMoveStart to make it available to target resolution and custom previews. The same source continues into the accepted drag; canceled pickups do not carry their gesture data into the next attempt. For example:
To replace the payload itself, call updatePayload. Unlike drag data, the new payload persists after the drag ends, until the payload prop changes.
Activation
A pointer press doesn’t start a drag right away. By default:
- Mouse and pen start a drag after 5px of movement.
- Touch starts a drag after a 250ms hold with less than 5px of movement. Moving further before that lets the page scroll instead.
Releasing before the threshold keeps the normal click or tap.
Pass activation to change the thresholds. It takes one activation method for every pointer type, or a map keyed by mouse, touch, and pen. Unlisted pointer types keep their defaults.
The available methods are:
{ type: 'distance', distance }starts the drag after the pointer moves by that many pixels.{ type: 'press-hold', delay, tolerance? }starts the drag after holding still fordelaymilliseconds. Moving more thantolerancepixels (5 by default) cancels the gesture.{ type: 'immediate' }starts the drag onpointerdown. Use it for a canvas tile whose whole area is meant to be dragged. Pressing a nested button, link, or input still doesn’t start a drag.{ type: 'double-click' }starts the drag on a double-click or double-tap. See Double-click pickup.
Pass an array to allow several methods at once. The first one to complete starts the drag. An empty array disables pickup.
Double-click pickup
With { type: 'double-click' }, a mouse double-click picks the item up. It then follows the pointer without a held button and drops on the next click. With touch or pen, the second tap of a double-tap picks the item up while the pointer is still down, and releasing drops it. Esc or Tab cancels either gesture.
Combine it with another method to keep drag-to-pick-up as well, or use a per-pointer map to enable it for some pointer types only:
Avoid this method on items whose double-click already opens or edits content. The click that drops the item is consumed, so it doesn’t also activate what’s underneath.
For these pickups, eventDetails.reason in onBeforeMoveStart and onMoveStart is 'double-click' instead of 'pointer'.
Constraining movement
A drag follows the pointer freely by default. Pass modifiers to <Draggable.Root> to keep it on one axis, snap it to a grid, or contain it within an element. Modifiers affect both the preview and the point used to find drop targets.
The built-in modifiers are:
restrictToVerticalAxisandrestrictToHorizontalAxislock the drag to one axis.restrictToParentElementkeeps the drag inside the source’s parent element.restrictToElement(element)keeps the drag inside any element. It accepts an element, a ref, or a function returning one.restrictToWindowEdgeskeeps the drag inside the browser viewport.snapToGrid(size)snaps the drag to a grid anchored at the pickup point. Pass a number, or{ x, y }for a rectangular grid.
Pass an array to apply several modifiers in order. Each one receives the previous one’s result:
In the demo below, restrictToElement keeps each widget inside the dashed frame. The drop position is constrained too, so the slot outside the frame never activates.
<Draggable.Preview> also accepts modifiers. Those only constrain the preview, while the drop position still follows the pointer. Use them when leaving an area should cancel the drag rather than drop on the nearest target. For instance, a data grid can keep the preview inside the grid while a release outside it cancels.
Custom modifiers
A modifier is a function that receives the proposed point and returns the point to use. It also receives the initialPoint where the drag started, the raw pointer input, the source and preview rectangles, the element’s scale, and the modifier keys held during the current event. It runs on every frame, so keep it cheap.
Points are in client pixels, matching getBoundingClientRect(). On a zoomed or scaled canvas, multiply a canvas distance by scale to convert it to client pixels. scale is 1 when nothing is scaled.
The ctrlKey, shiftKey, altKey, and metaKey flags let a modifier react to keys held during the drag. Pressing or releasing a key reapplies the modifiers on the next frame.
Drag preview
While dragging, a copy of the source element follows the pointer. This clone keeps the element’s classes, form values, canvas drawings, and scroll positions. Base UI renders it in the browser’s top layer, so no scroll container clips it and nothing on the page paints over it.
Base UI rewrites the clone’s IDs to keep the document valid, so style it with classes or [data-drag-preview] rather than ID selectors. Styles that depend on :hover or :focus don’t apply to it. The clone also leaves out content that lives outside the DOM, such as a WebGL canvas, a playing video, or a shadow DOM. Render a custom preview in those cases, or when the source is heavy enough that cloning it takes a visible moment.
Render <Draggable.Preview> without children to configure how the clone is placed. It renders no element of its own in that case:
Pass disabled to drag without any preview. The drag still runs and targets still respond, which suits a canvas that draws its own feedback:
Custom preview
Pass children to <Draggable.Preview> to show something other than a clone. Keep pointer-events: none on the preview so it doesn’t block the drop targets under it. The dashboard below drags each widget as a compact badge:
Pass a function as children to build the preview from the dragged item. It receives the drag source and runs once when the drag starts. Return null to show no preview for that particular drag.
A custom preview stays mounted even if the source unmounts during the drag. It reads React context from above the nearest <Draggable.Provider>, so place that provider inside any contexts the preview needs.
The preview takes the size of its content. To match the source instead, use the --drag-source-width and --drag-source-height CSS variables:
Preview position
Use the offset prop to position the preview relative to the pointer. It works the same for the clone and for custom content.
Preview container
Base UI inserts the preview element next to the source in the DOM, so your CSS reaches it the same way. This extra sibling has two side effects during the drag:
- Selectors that count from the end, such as
:last-childand:nth-last-child, shift by one. - DOM queries over the siblings include the preview. Exclude it with
:not([data-drag-preview])when measuring.
Pass container to insert the preview somewhere else. It accepts an element, a ref, or a function that receives the source element:
Prefer the closest suitable container. The preview loses the source’s ancestors, so descendant selectors such as .Column .Card stop matching. Rules based on the preview’s own classes still apply.
Drag cursor
During a mouse or pen drag, the cursor is grabbing across the whole document, whatever is under the pointer. Pass a different CSS cursor as dragCursor, or false to manage the cursor yourself. Set the resting cursor in your own styles:
Base UI injects the cursor rule as a <style> element. See the CSP guide if your content security policy disallows it.
Styling the source and preview
[data-dragging] is present on the source element during the drag. The clone never gets it, so a rule that dims the source leaves the preview intact. [data-drag-preview] is present on the preview only:
Base UI prevents text selection on draggables and disables page scrolling during a drag. Avoid setting touch-action on the draggable or its handle, since it interferes with touch dragging. Set it on a wrapper instead.
Animating a drop
After a release, the clone moves to the source’s final position and receives [data-ending-style]. A release outside any target moves it back to the source. Add a translate transition to animate this, and Base UI keeps the clone mounted until the transition ends:
The source also receives [data-ending-style] while the clone settles, so it can act as an empty placeholder until the clone arrives:
Base UI adds no animation of its own. Wrap the transitions you add in a prefers-reduced-motion: no-preference media query to respect the user’s preference.
Drop position within a target
target.getLocalPoint() returns where the pointer is within the target, as a fraction of its size. Each axis is 0 at the left or top edge and 1 at the right or bottom edge. Use it when a drop means a value rather than the target itself, such as a time within a day column:
The value isn’t clamped, because an outer target can have the pointer outside its own box while a nested target is under it. Clamp it where your data requires it.
When the target represents fixed intervals such as 15-minute slots or weekday columns, declare snap and read getSnappedLocalPoint() instead. It rounds the fraction to the nearest step and clamps it between 0 and 1. Step counts divide the target’s box, so you don’t need to know its size in pixels:
snap also accepts a function that receives the drag source, for a step count that depends on the item.
When moving an element, the value to commit is usually where the element lands rather than where the pointer is. Pass { anchor: 'source' } to snap the dragged element’s top-left corner instead of the pointer:
snap only changes the value this target reports. To snap the preview itself, use the snapToGrid modifier.
Nested drop targets
When targets are nested, the innermost one that accepts the drag receives the drop. Return false from canDrop to skip a target and let an ancestor receive the drop instead. In the demo, the frame accepts only the chart, so a note released over the frame lands on the canvas behind it.
Returning false means “skip this target” rather than “block this area”. A target nested inside it can still receive the drop. Return 'reject' when a rule must block the drop everywhere within the target. For example, a full column should reject cards even over the cards it already contains. A rejecting target receives [data-rejected] while the drag is over it:
Styling drop targets
Targets expose their state through data attributes:
[data-accepting]is present on every target that accepts the current drag, from pickup until the drag ends. Use it to reveal every possible destination as soon as a drag starts.[data-drag-over]is present while the drag is over the target or one of its nested targets.[data-drag-over-innermost]is present only on the innermost target, which is the one that would receive the drop.[data-rejected]is present whilecanDropreturns'reject'for the current position.
[data-accepting] is based on accept alone. A target can carry it and still refuse the drop when canDrop runs.
Tracking these states re-renders the target as the drag moves. Set trackDragOver={false} on targets that don’t use them, for example when every row of a long list is a target. Their handlers still fire and they still receive drops:
Native file drops
Base UI drags carry no dataTransfer, so they can’t exchange data with other windows or applications. To accept files from the desktop, add native onDrop and onDragOver handlers next to onDraggableDrop. Both kinds of drop can coexist on the same target:
One element with two roles
Use the render prop to give one element two roles. For example, an item that other items can be dropped on:
Don’t use this pattern to build a sortable list. <Draggable.CollisionProvider> handles that case, including the dragged item’s own position and which side of an item the pointer is on. See Sorting a list.
The same applies to the other parts. A scrollable list that accepts drops can be both a target and a viewport:
Sorting a list
Use <Draggable.CollisionProvider> to reorder a group of draggables. Pass the same kind to the provider and to each <Draggable.Root> inside it. The provider reports which item is under the pointer, and your handlers decide where to insert the dragged item.
Update the order in onMoveEnd. The event’s collision.target.payload identifies the item under the pointer, and collision.target.getLocalPoint() tells which half of it the pointer is in. For a vertical list, a y above 0.5 means the bottom half. collision is null when the drag was canceled, released outside the group, or released over the dragged item itself.
To show an insertion indicator, compute the position in onCollisionChange and store it in state. The event includes the previousCollision, so you can skip updates when the position hasn’t changed:
By default, the provider measures each item’s own element. Pass collisionElement to <Draggable.Root> to measure a wrapper instead, for example a padded row so that the gaps between items count too. An empty list has no item to collide with, so render a <Draggable.Target> for it.
Sorting while dragging
To move items as the drag goes, update the order in onCollisionChange instead of onMoveEnd. Compare location.current.input with location.previous.input to place the item according to the pointer’s direction.
Save the original order in onMoveStart, and restore it in onMoveEnd when event.canceled is true or event.dropTarget is null. A release over the dragged item itself has a dropTarget but a null collision. Keep the current order in that case.
When items animate into their new positions, pass collisionElement to measure a wrapper that stays still during the animation. For long lists, memoize the items and keep their handlers stable so that a reorder only moves DOM nodes.
Both demos let the keyboard reorder the focused item with Alt+↑ and Alt+↓.
Auto-scroll direction
CSS decides which axes can scroll. With overflow-x: auto and overflow-y: hidden, only horizontal scrolling is possible. To restrict further, cancel a direction in onDragScroll. Base UI calls the handler once per direction on every scrolling frame:
Drag a stop toward the left or right edge and the lane scrolls to follow. It only scrolls sideways, so moving the pointer up or down never scrolls it.
The handler also receives the drag source, so the allowed direction can depend on what’s being dragged. A grid can scroll vertically for rows and horizontally for columns:
Auto-scroll speed
maxSpeed sets the auto-scroll speed reached at the container’s edge, in pixels per second. It defaults to 900. Scrolling accelerates as the pointer approaches the edge. Lower it for a short list or raise it for a large scroll range:
It also accepts a function, called on every scrolling frame:
A maxSpeed of 0 stops the container and lets an ancestor scroll instead, like canceling in onDragScroll.
Auto-scroll outside the viewport
overflowMargin lets scrolling continue when the drag moves outside a container. Pass a distance in CSS pixels for every edge, or an object to extend individual physical edges:
The default is 0. Omitted edges, negative values, and non-finite values are treated as 0. With only top and bottom configured, the drag must stay within the viewport’s horizontal bounds. To include the corners, extend the corresponding horizontal edges too:
Inside the viewport, the edge zones and speed stay the same. Outside an enabled edge, scrolling keeps its maximum engagement and existing speed ramp, up to maxSpeed. It stops beyond the margin. Updating overflowMargin on a mounted Draggable.Viewport takes effect without another pointer move.
Viewports containing the drag position get first use of each scrolling direction. Outside margins then compete for unclaimed directions, innermost first. Scroll limits and onDragScroll’s cancel() and consume() behavior still apply. Drag modifiers participate in deciding which viewport can scroll, just as they do without margins.
The margin changes only auto-scrolling: it does not enlarge drop targets, move the preview, or change the coordinates passed to onDragScroll. It does not change document/page scrolling, which already continues when a captured pointer leaves the window.
Custom auto-scrolling
When Base UI can’t scroll an element itself, for example a canvas panned with a CSS transform, use onDragScroll to apply the movement yourself. The element doesn’t need scrollable overflow. Edge zones, acceleration, and nesting work the same as for a scroll container.
Drag a pin to the bottom edge and hold still. The canvas has nothing to scroll, so it moves its own camera, and the archive scrolls into reach.
Archived: nothing yet
Render the viewport on the element that clips the canvas, not on the transformed content. In the handler, cancel the default, apply the movement, and call consume() to claim that direction so an ancestor viewport doesn’t scroll on it:
x and y are the distances Base UI would have passed to scrollBy() this frame, in pixels. A positive x moves the view right, so the content moves left. Apply them synchronously and from a ref rather than through React state, since Base UI looks for drop targets again on the next frame.
The handler runs once per direction, with the other axis set to 0. At a bound the canvas can’t move past, skip consume() so that an ancestor can scroll instead:
API reference
Provider
Groups the drag sources, drop targets, and viewports of an interaction.
It provides the default kind used by parts that declare none, and gives custom
previews access to React context. Required above the Draggable parts and
useDragDropManager. Doesn’t render its own HTML element.
childrenReact.ReactNode—
- Name
- Description
The parts of the interaction.
- Type
Root
An element that can be picked up with the pointer and dropped on a matching drop target.
While dragging, a clone of the element follows the pointer by default.
Renders a <div> element.
activationUnion—
- Name
- Description
Determines when a pointer press starts a drag. Accepts one activation method for every pointer type, a map with a method per pointer type, or an array to allow several methods. By default, mouse and pen start after 5px of movement, and touch after a 250ms hold.
- Type
collisionbooleantrue
- Name
- Description
Whether other items of the nearest matching collision provider can be dropped on this one.
- Type
- Default
true
collisionElementfunction—
- Name
- Description
Returns the element measured for collisions, for example a padded row wrapper so that the gaps between items count too. Defaults to the root’s own element.
- Type
collisionPayloadTPayload—
- Name
- Description
The payload reported by the collision provider when another item is dragged over this one. Defaults to
payload.- Type
dragCursorstring | false'grabbing'
- Name
- Description
The CSS cursor shown across the document during a mouse or pen drag. Pass
falseto manage the cursor yourself.- Type
- Default
'grabbing'
kindUnion—
- Name
- Description
The kind of this item, created with
Draggable.createKind. Defaults to the kind of the nearest<Draggable.Provider>, which carries no payload.- Type
modifiersDragModifiers—
- Name
- Description
One or more modifiers that constrain the drag, applied in order. They affect both the preview and the drop position. See Constraining movement.
- Type
onBeforeMoveStartfunction—
- Description
Event handler called just before a drag starts, once the activation threshold is met. Call
eventDetails.cancel()to prevent the drag.- Type
onMovefunction—
- Name
- Description
Event handler called as the pointer moves or a modifier key changes, at most once per animation frame. Use a drop target’s
onDraggableMovefor hover feedback.- Type
onMoveEndfunction—
- Name
- Description
Event handler called once when the drag ends, after a drop, a release outside any target, or a cancellation.
eventDetails.reasonis'drop'for a successful drop.A drag canceled during pickup fires this handler without a preceding
onMoveStart.- Type
onMoveStartfunction—
- Name
- Description
Event handler called once when the drag starts. The preview exists by then, so the source can be measured or restyled safely.
- Type
onTargetChangefunction—
- Name
- Description
Event handler called when the drop targets under the pointer change.
- Type
payloadTPayload—
- Name
- Type
previewKeystring | number—
- Name
- Description
A stable key that lets the settling preview find this item again after it remounts, for example when a virtualized or reordered list recreates it. Use the same key for the same item.
- Type
snapUnion—
- Name
- Description
Divides this item into equal steps for
getSnappedLocalPoint()when another item is dragged over it. Accepts step counts or a function returning them. Doesn’t affect the preview’s position.- Type
disabledbooleanfalse
- Name
- Description
Whether dragging is disabled. Pointer presses keep their normal behavior. Use
onBeforeMoveStartwhen the decision depends on the gesture.- Type
- Default
false
childrenReact.ReactNode—
- Name
- Type
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
data-dragging
Present on the source element while it is being dragged.
A cloned preview never carries this attribute, so a [data-dragging]
rule that dims or hides the source leaves the preview fully visible.
data-disabled
Present while the draggable is disabled.
data-ending-style
Present on the source after a deliberate release while a clone created by Base UI settles into its final position, including a return after release outside a target. Use it to keep the source styled as a placeholder until the preview’s ending animation finishes.
Attribute | Description | |
|---|---|---|
data-dragging | Present on the source element while it is being dragged.
A cloned preview never carries this attribute, so a | |
data-disabled | Present while the draggable is disabled. | |
data-ending-style | Present on the source after a deliberate release while a clone created by Base UI settles into its final position, including a return after release outside a target. Use it to keep the source styled as a placeholder until the preview’s ending animation finishes. | |
Draggable.Root.StateHide
Draggable.Root.BeforeMoveStartEventHide
Draggable.Root.BeforeMoveStartEventDetailsHide
Draggable.Root.BeforeMoveStartEventReasonHide
Draggable.Root.MoveEndEventHide
Draggable.Root.MoveEndEventDetailsHide
Draggable.Root.MoveEndEventReasonHide
Draggable.Root.MoveEventHide
Draggable.Root.MoveEventDetailsHide
Draggable.Root.MoveEventReasonHide
Draggable.Root.MoveStartEventHide
Draggable.Root.MoveStartEventDetailsHide
Draggable.Root.MoveStartEventReasonHide
Draggable.Root.PropsWithPayloadHide
Draggable.Root.TargetChangeEventHide
Draggable.Root.TargetChangeEventDetailsHide
Draggable.Root.TargetChangeEventReasonHide
Handle
The area of a draggable that starts a drag. The rest of the draggable stays interactive.
Omit it to make the whole draggable start a drag.
Renders a <span> element.
disabledundefined—
- Name
- Description
Not supported. A handle follows the disabled state of its
<Draggable.Root>.- Type
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
data-disabled
Present while the handle’s Draggable.Root is disabled. A handle follows
the disabled state of its root.
Attribute | Description | |
|---|---|---|
data-disabled | Present while the handle’s | |
Draggable.Handle.StateHide
Preview
Configures what follows the pointer during a drag.
Without children, it configures the default clone of the source and renders nothing.
With children, it renders them in a <div> element inserted beside the source
while dragging. That element reads React context from above the nearest <Draggable.Provider>.
kindDragKind<TPayload, TDragData>—
- Name
- Description
The kind of the dragged item, which types
source.payloadin the render function. Drags of other kinds show no preview.- Type
modifiersDragModifiers—
- Name
- Description
One or more modifiers that constrain the preview only. The drop position still follows the pointer. To constrain the drag itself, use
modifiersonDraggable.Root.- Type
offsetDragPreviewOffset'source'
- Name
- Description
Where the preview sits relative to the pointer.
- Type
- Default
'source'
containerDragPreviewContainer—
- Name
- Description
Where to insert the preview element in the DOM. Defaults to beside the source, so the same CSS applies to it. Pass a container to keep selectors such as
:last-childon the source’s siblings unchanged during the drag.- Type
disabledbooleanfalse
- Name
- Description
Whether to show no preview. The drag still runs.
- Type
- Default
false
children| React.ReactNode
| ((parameters: DragPreviewRenderEvent<TPayload, TDragData>) => React.ReactNode)
| ((parameters: DragPreviewRenderEvent) => React.ReactNode)—
| ((parameters: DragPreviewRenderEvent<TPayload, TDragData>) => React.ReactNode)
| ((parameters: DragPreviewRenderEvent) => React.ReactNode)
- Name
- Description
The preview content. Pass a function to build the content from the drag source when the drag starts. It can return
nullto show no preview for that drag.- Type
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
data-drag-preview
Present on the drag preview element. A cloned preview keeps the source’s classes, so use this attribute to distinguish them in CSS.
data-ending-style
Present on a cloned preview created by Base UI after a deliberate release while it moves to its final position. This also applies when a drag is released outside a target and returns to its source. The clone remains mounted until animations started by this state finish.
Attribute | Description | |
|---|---|---|
data-drag-preview | Present on the drag preview element. A cloned preview keeps the source’s classes, so use this attribute to distinguish them in CSS. | |
data-ending-style | Present on a cloned preview created by Base UI after a deliberate release while it moves to its final position. This also applies when a drag is released outside a target and returns to its source. The clone remains mounted until animations started by this state finish. | |
--drag-source-height
The height of the element the drag was lifted from.
--drag-source-width
The width of the element the drag was lifted from.
CSS Variable | Description | |
|---|---|---|
--drag-source-height | The height of the element the drag was lifted from. | |
--drag-source-width | The width of the element the drag was lifted from. | |
Draggable.Preview.StateHide
Draggable.Preview.RenderEventHide
Target
An area where a matching draggable can be dropped.
Renders a <div> element.
acceptUnion—
- Name
- Type
canDropfunction—
- Name
- Description
Decides whether the current drag can be dropped on this target. Runs after
accept.Return
falseto skip this target and let an ancestor receive the drop. Return'reject'to block the drop on this target, its nested targets, and its ancestors, for example when a column is full. The target then has[data-rejected].- Type
kindUnion—
- Name
- Description
The kind of this target, created with
Draggable.createKind. Use itsmatchesmethod to tell target kinds apart in a shared handler, which also typestarget.payload. Not to be confused withaccept, which lists the kinds of draggable this target takes.- Type
onDraggableDropfunction—
- Name
- Description
Event handler called when the drag is released over this target. Only the innermost target under the pointer receives it, and it never fires on a cancel. Use the source’s or a monitor’s
onMoveEndto observe every drag end.- Type
onDraggableEnterfunction—
- Name
- Description
Event handler called when the drag moves over this target.
- Type
onDraggableLeavefunction—
- Name
- Description
Event handler called when the drag moves off this target, or ends.
eventDetails.reasontells which.- Type
onDraggableMovefunction—
- Name
- Description
Event handler called on every animation frame the pointer moves or a modifier key changes while the drag is over this target, starting with the frame it enters. Put hover feedback such as drop indicators here.
- Type
onDraggableStartfunction—
- Name
- Description
Event handler called when a drag starts while this target is already under the pointer. Use a monitor’s
onMoveStartto observe drags starting elsewhere.- Type
payloadTTargetPayload—
- Name
- Type
snapUnion—
- Name
- Description
Divides the target into equal steps for
getSnappedLocalPoint(). For example,{ y: 96 }splits a day column into 15-minute slots, whatever its height. Accepts step counts or a function receiving the drag source.It only changes the value this target reports. Use the
snapToGridmodifier to snap the preview itself.- Type
trackDragOverbooleantrue
- Name
- Description
Whether to track the drag-over state and expose it through data attributes. Disable it on targets that don’t use them to avoid re-rendering as the drag moves.
- Type
- Default
true
disabledbooleanfalse
- Name
- Description
Whether the target ignores drags. A disabled target is skipped, so drags fall through to ancestor targets.
- Type
- Default
false
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
data-disabled
Present while the drop target is disabled.
data-accepting
Present while a drag this target accepts is active, regardless of pointer
position. Use it to highlight every compatible drop target.
Absent when trackDragOver is false.
data-drag-over
Present while a matching drag source is over the target or a nested descendant.
Absent when trackDragOver is false.
data-drag-over-innermost
Present while the target is the innermost one under the source.
Absent when trackDragOver is false.
data-drop-target
Present while the element is registered as a drop target. Base UI also uses it to resolve targets during hit testing.
data-rejected
Present while canDrop returns 'reject' for the current position. Use it
to display feedback such as a full column. Absent when trackDragOver is
false.
Attribute | Description | |
|---|---|---|
data-disabled | Present while the drop target is disabled. | |
data-accepting | Present while a drag this target accepts is active, regardless of pointer
position. Use it to highlight every compatible drop target.
Absent when | |
data-drag-over | Present while a matching drag source is over the target or a nested descendant.
Absent when | |
data-drag-over-innermost | Present while the target is the innermost one under the source.
Absent when | |
data-drop-target | Present while the element is registered as a drop target. Base UI also uses it to resolve targets during hit testing. | |
data-rejected | Present while | |
Draggable.Target.StateHide
Draggable.Target.DropEventHide
Draggable.Target.DropEventDetailsHide
Draggable.Target.DropEventReasonHide
Draggable.Target.EnterEventHide
Draggable.Target.EnterEventDetailsHide
Draggable.Target.EnterEventReasonHide
Draggable.Target.LeaveEventHide
Draggable.Target.LeaveEventDetailsHide
Draggable.Target.LeaveEventReasonHide
Draggable.Target.MoveEventHide
Draggable.Target.MoveEventDetailsHide
Draggable.Target.MoveEventReasonHide
Draggable.Target.PropsWithPayloadHide
Draggable.Target.StartEventHide
Draggable.Target.StartEventDetailsHide
Draggable.Target.StartEventReasonHide
Viewport
A scroll container that scrolls automatically when a drag nears its edges.
Each container, including nested ones, needs its own viewport.
Renders a <div> element.
acceptUnion—
- Name
- Description
One or more kinds of draggable that scroll this container. Omit it to scroll for every drag.
- Type
maxSpeedUnion900
- Name
- Description
The scrolling speed reached at the container’s edge, in pixels per second. Accepts a number or a function called on every scrolling frame.
0stops this container and lets an ancestor viewport scroll instead.- Type
- Default
900
onDragScrollfunction—
- Name
- Description
Event handler called once per direction on every scrolling frame. Call
eventDetails.cancel()to prevent scrolling in that direction, or to apply the movement yourself for an element Base UI can’t scroll, such as a panned canvas. After moving, calleventDetails.consume()to keep an ancestor viewport from scrolling on the same axis. Skip it at a bound the element can’t move past.- Type
overflowMarginAutoScrollOverflowMargin0
- Name
- Description
How far outside the container a drag can continue auto-scrolling, in CSS pixels. A number applies to every edge; an object sets physical edges independently. Omitted, negative, and non-finite edge values are treated as
0. Outside an edge, scrolling keeps its maximum engagement and existing speed ramp. Viewports containing the drag position take priority over outside margins. Does not change drop targets, layout, or document/page scrolling.- Type
- Default
0
disabledbooleanfalse
- Name
- Description
Whether auto-scrolling is disabled. An ancestor viewport can then scroll instead. Changing it during a drag pauses or resumes scrolling. Use
onDragScrollfor a decision that depends on the drag.- Type
- Default
false
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
data-disabled
Present while auto-scrolling is disabled.
Attribute | Description | |
|---|---|---|
data-disabled | Present while auto-scrolling is disabled. | |
Draggable.Viewport.StateHide
Draggable.Viewport.DragScrollEventHide
Draggable.Viewport.DragScrollEventDetailsHide
Draggable.Viewport.DragScrollEventReasonHide
CollisionProvider
Groups draggables of the same kind and reports which one is under the pointer, for sorting. Doesn’t render its own HTML element.
canCollidefunction—
- Name
- Description
Whether the dragged item can be dropped on a given item of this group. Return
falseto skip the item, or'reject'to block the drop.- Type
kind*DragKind<TPayload, TDragData>—
- Name
- Description
The kind of the items in this group. Pass the same kind to each
<Draggable.Root>.- Type
onCollisionChangefunction—
- Description
Event handler called when the item under the pointer changes, including when the pointer leaves the group. Compare
collisionwithpreviousCollisionto skip updates when the insertion position hasn’t changed.- Type
onMoveEndfunction—
- Name
- Description
Event handler called when a drag that involved this group ends. Use
collisionto apply the final position, orcanceledto restore the original order.- Type
onMoveStartfunction—
- Name
- Description
Event handler called when an item of this group starts dragging, or when a drag that started elsewhere first enters the group.
- Type
childrenReact.ReactNode—
- Name
- Type
Draggable.CollisionProvider.PropsHide
Re-Export of CollisionProvider props as DraggableCollisionProviderProps
Draggable.CollisionProvider.CollisionHide
Draggable.CollisionProvider.CollisionChangeEventHide
Draggable.CollisionProvider.CollisionChangeEventDetailsHide
Draggable.CollisionProvider.CollisionChangeEventReasonHide
Draggable.CollisionProvider.CollisionEventHide
Draggable.CollisionProvider.MoveEndEventHide
Draggable.CollisionProvider.MoveEndEventDetailsHide
Draggable.CollisionProvider.MoveEndEventReasonHide
Draggable.CollisionProvider.MoveStartEventHide
Draggable.CollisionProvider.MoveStartEventDetailsHide
Draggable.CollisionProvider.MoveStartEventReasonHide
createKind
Creates a kind to pass to a draggable’s kind prop and to a target’s accept prop.
Parameters
name*string—
- Name
- Type
Return value
createGlobalKind
createKind matches kinds by identity: a target accepts a source only if both were given the same kind object, usually a constant they both import. That’s impossible when the source and the target live in code that doesn’t share modules, for example a plugin loaded at runtime, a micro-frontend, or two copies of the same package on one page. createGlobalKind solves this by using a string key as the identity. Two calls with the same key, from anywhere on the page, produce kinds that match each other:
Keys are shared by the whole page, so a bare name like 'card' could collide with a kind from another library. Prefix keys with your app or package name. Both sides must also agree on the payload type: TypeScript can’t check that across bundles, so a mismatch surfaces at runtime.
Prefer createKind whenever the source and the target can import the same constant.
Parameters
key*string—
- Name
- Description
A key such as
'myapp/card'.- Type
Return value
anyKind constant
A kind that matches every drag. Pass it to a target’s accept prop to accept everything. The resulting source.payload is unknown until narrowed with a specific kind’s matches method.
Modifiers
The built-in movement modifiers. Pass them to the modifiers prop of <Draggable.Root> or <Draggable.Preview>.
restrictToVerticalAxis
Locks the drag to the vertical axis.
Parameters
context*DragModifierContext—
- Name
- Type
Return value
restrictToHorizontalAxis
Locks the drag to the horizontal axis.
Parameters
context*DragModifierContext—
- Name
- Type
Return value
restrictToParentElement
Keeps the drag inside the source element’s parent.
Parameters
context*DragModifierContext—
- Name
- Type
Return value
restrictToElement
Keeps the drag inside an element. Accepts the element, a ref to it, or a function returning it. The element is measured on every move, so it can scroll or resize during the drag.
Parameters
element*DragElementReference—
- Name
- Type
Return value
restrictToWindowEdges
Keeps the drag inside the browser viewport.
Parameters
context*DragModifierContext—
- Name
- Type
Return value
snapToGrid
Snaps the drag to a grid anchored where the drag started. Pass a number for a
square grid, or { x, y } for a rectangular one. A step of 0 leaves that axis free.
The step is in the source’s own units, so snapToGrid(20) still snaps to a
20-unit grid on a zoomed canvas.
Parameters
size*Union—
- Name
- Type
Return value
DragModifier
The type of a custom modifier.
Parameters
context*DragModifierContext—
- Name
- Type
Return value
useActiveDrag
Returns the source of the drag in progress, or null when nothing is being dragged. The component re-renders when a drag starts or ends. It works anywhere on the page, with or without a <Draggable.Provider>.
Pass a kind, or an array of kinds, to observe only those drags and type source.payload. Other drags return null.
Parameters
accept*AnyDragAccept—
- Name
- Type
Return value
Draggable.useActiveDrag.ReturnValueHide
useDragMonitor
Observes drags anywhere on the page without an element or a <Draggable.Provider>. Use it for status indicators, analytics, or handling drops in one place.
Pass accept to filter by kind and infer the type of source.payload. Omit it to observe every drag with an unknown payload.
Monitors support the same handlers as <Draggable.Root>, except onBeforeMoveStart. A monitor mounted during a drag observes the remaining events, so onMoveEnd can fire without a preceding onMoveStart.
Parameters
parameters*DragParametersWithInferredAccept<
UseDragMonitorParameters<
TPayload | unknown,
TDragData | unknown
>,
AnyDragAccept
>—
- Name
- Type
Return value
Draggable.useDragMonitor.ParametersHide
Draggable.useDragMonitor.ReturnValueHide
useDragDropManager
Returns the page-wide drag manager. Use it to register existing DOM elements as drag sources, drop targets, or scroll containers. The hook requires a <Draggable.Provider> above the calling component.
All calls share the same manager, and cancelDrag() ends the active drag. Use distinct kinds to keep unrelated features separate.
Source, target, and scroll registrations take an element and a function returning its options. Monitor registration takes only the options function. Each method returns a cleanup function. Register in an effect and return the cleanup:
The manager and its registration methods are stable and can be listed as effect dependencies. Base UI reads each option at the following times:
Option | When it’s read |
|---|---|
Event handlers and predicates, such as canDrop | Every time they’re needed, so they see the latest closure. |
A source’s kind and preview settings | Once when a drag starts. |
A source’s payload | At pickup, and whenever it changes during the drag. |
A target’s kind and payload | Each time the target is evaluated, including on drop. |
disabled, dragHandle, and the styles applied while idle | At registration and on the next pointerdown. Re-register to apply a change at once. |
registerDraggable
Registers a drag source. It accepts the options of <Draggable.Root>, plus dragHandle to restrict pickup to an element, and dragPreview to configure the preview:
dragPreview takes the options of <Draggable.Preview>. Pass a render function instead of children to show custom content:
registerDropTarget
Registers a drop target. It accepts the options of <Draggable.Target>, except trackDragOver, since there is no React state to update. accept is required here.
TypeScript infers source.payload from accept but can’t infer the target’s own payload through the options function. Pass both type arguments, as in registerDropTarget<typeof card, SlotPayload>, when the target reads target.payload.
registerAutoScroller
Registers a scroll container, with the options of <Draggable.Viewport>. Use it to scroll the page, or for a container rendered by code you don’t control:
registerMonitor
Registers a monitor with the options of useDragMonitor:
cancelDrag
Ends the drag in progress. onMoveEnd fires with canceled: true and the 'imperative-action' reason. It does nothing when no drag is active. Use it when a route change, a dialog, or a deleted record invalidates the drag:
Return value
UseDragDropManagerReturnValue
registerDraggablefunction
- Description
Registers an element as a drag source, with the options of
Draggable.Root. Returns a cleanup function that unregisters it.- Type
registerDropTargetfunction
- Description
Registers an element as a drop target, with the options of
Draggable.Target. Returns a cleanup function that unregisters it.- Type
registerAutoScrollerfunction
- Description
Registers a scroll container, with the options of
Draggable.Viewport. Passdocument.documentElementto scroll the page. Returns a cleanup function that unregisters it.- Type
registerMonitorfunction
- Name
- Description
Registers a monitor, with the options of
useDragMonitor. Returns a cleanup function that unregisters it.- Type
cancelDragfunction
- Name
- Description
Cancels the drag in progress, if any.
onMoveEndfires withcanceled: trueand the'imperative-action'reason.- Type