Skip to contents

Styling

Styling the states a drag moves through.

Drag and drop adds no visual styles. Base UI exposes each state as a data attribute. See the styling guide for how to use data attributes. The Draggable, DropTarget, and DragAutoScroll API references list every attribute.

The sections below cover the attributes that have to be combined to be useful.

Dimming the source

data-dragging marks the source element. Base UI removes it from the cloned preview, so a [data-dragging] rule dims the source without changing the preview:

Dimming the source

A custom preview renders its own content, so it never has the source’s classes; only [data-drag-preview] reaches it.

Sizing a custom preview

The clone is the size of the element it copies; a custom preview is sized by its own content. To match the source, read the size the engine writes onto the preview element as --drag-source-width and --drag-source-height:

Matching the source size

Base UI sets both values once at drag start from the source’s bounding rect. For a compact preview such as a badge or count, use offset="pointer" instead.

Easing keyboard drags

A transition makes a pointer preview lag behind the cursor, but it can make keyboard movement between drop targets easier to follow. Use data-drag-mode to apply the transition only to a keyboard preview:

Easing the preview for keyboard drags only

Transition only translate, which Base UI uses to position the preview. The .Card selector also matches the source, so an opacity transition would continue after the preview is removed and make the dropped item fade in.

The same selector can mirror the focus ring onto the preview, so a keyboard drag stays visibly “focused” while the item moves:

A focus ring for the keyboard-dragged preview

Animating a drop

After a release, an engine-owned clone receives data-ending-style and moves to the source’s committed position. A transition in that state keeps the clone mounted until it finishes:

Settling a cloned preview after drop

The source keeps data-dragging and also receives data-ending-style while the clone settles. This lets a destination render as an empty placeholder until the clone arrives:

Keeping the destination as a placeholder

Without a transition, both ending attributes are removed before the next paint. This applies to Draggable.ClonedPreview; a custom Draggable.Preview unmounts when the drag ends.

Animating displaced items

CSS cannot determine the previous position of an item moved by a reorder. Add Draggable.Displacement so Base UI measures the item during drags. It publishes the offset as --drag-displacement-x and --drag-displacement-y, adds data-starting-style for the first frame, and keeps data-displacing during the animation:

Easing displaced rows to their new slots

Use translate instead of transform so the animation composes with existing transform positioning. The second rule replaces the element’s transition shorthand, so repeat any other transitions the element needs. See Animating displaced items for interrupted reorders. For reduced motion, disable both rules rather than only the transition.

Highlight the innermost target

Several nested drop targets can be active at once. For example, a card over a slot inside a column marks both targets. data-drag-over is present on every target in the stack, while data-drag-over-innermost is present only on the target that would receive the drop:

Feedback on both levels

Both attributes are absent on targets with trackDragOver={false}.

The drag cursor

While a pointer drag runs, the engine pins the cursor across the document so it stays the same over every element the pointer crosses: grabbing, or whatever you pass as dragCursor. Touch has no cursor, so this is skipped there. The resting cursor is yours to set:

The resting cursor

Styles the engine writes

Base UI writes inline styles and injects one cursor stylesheet. These styles may override the same properties in your CSS. Draggable.Displacement also sets --drag-displacement-x and --drag-displacement-y on each displaced element during an animation. The variables inherit to descendants and are removed with the data attributes when the animation ends.

At registration, each enabled draggable receives touch-action: manipulation, user-select: none, and -webkit-touch-callout: none. A disabled source receives none of these styles or the keyboard-drag ARIA attributes. The styles prevent text selection and the iOS callout while still allowing touch scrolling before the 250ms drag delay ends. When a Draggable.Handle is present, Base UI applies the styles to the handle instead of the root. Setting touch-action on the handle overrides this behavior and can interfere with touch dragging or list scrolling, so set it on a wrapper instead. Base UI restores the original styles when the source unregisters.

During a pointer drag, Base UI sets touch-action: none, user-select: none, -webkit-touch-callout: none, and overscroll-behavior: none on <html> and <body>. This prevents page scrolling and overscroll. Base UI restores the values when the drag ends. Keyboard drags do not apply this lock.

A pointer drag also adds a baseui-dragging class to <html> and, once per document, injects a single rule:

The injected cursor rule

The universal selector with !important is what makes the drag cursor hold over elements that set their own (a handle’s grab, an input’s text). --drag-cursor is set on <html> for the duration of the drag from the source’s dragCursor, so you can also set it yourself for a scope the engine doesn’t know about. When the drag ends, whether it drops or is canceled, the classes are removed and the custom property is restored. The injected rule remains installed for reuse by later drags; keyboard drags write none of this state.

The stylesheet receives the nonce from CSPProvider. When disableStyleElements is enabled, Base UI does not inject or activate its cursor rule, but it still adds baseui-dragging and sets --drag-cursor. Add the following equivalent rule to an external stylesheet to retain the document-wide cursor:

External cursor rule

Reduced motion

The engine animates nothing, so a drag has no motion unless you add a transition. Guard the ones you add:

Guarding an added transition