Skip to main content
The @zendeskgarden/react-draggable package provides unstyled drag-and-drop primitives that you compose with your own drag-and-drop library (such as dnd-kit or react-beautiful-dnd). Garden handles visual styling and accessibility; you handle the drag logic.

Installation

npm install @zendeskgarden/react-draggable

# Peer dependencies
npm install react react-dom styled-components @zendeskgarden/react-theming

Draggable

Draggable is the individual drag item. It renders a div (by default) and is focusable via keyboard. Use Draggable.Grip to show the grip handle icon and Draggable.Content to wrap text or other content.
import { ThemeProvider } from '@zendeskgarden/react-theming';
import { Draggable } from '@zendeskgarden/react-draggable';

const App = () => (
  <ThemeProvider>
    <Draggable>
      <Draggable.Grip />
      <Draggable.Content>Drag me</Draggable.Content>
    </Draggable>
  </ThemeProvider>
);

States

<Draggable isGrabbed>
  <Draggable.Grip />
  <Draggable.Content>Currently dragging</Draggable.Content>
</Draggable>

Props

isGrabbed
boolean
Applies grab styling to indicate the item is currently being dragged.
isPlaceholder
boolean
Hides content and applies placeholder background styling to represent the original position during a drag.
isDisabled
boolean
Disables the draggable and applies aria-disabled.
isCompact
boolean
Applies compact (reduced) spacing.
isBare
boolean
Removes borders from the draggable item.
focusInset
boolean
Applies an inset box-shadow on focus instead of the default outset ring.
tag
any
default:"'div'"
Updates the rendered HTML element.

DraggableList

DraggableList renders a <ul> that provides context for its items. Place DraggableList.Item elements inside it, each containing a Draggable. Use DraggableList.DropIndicator to mark the target insertion point during a drag.
import { ThemeProvider } from '@zendeskgarden/react-theming';
import { Draggable, DraggableList } from '@zendeskgarden/react-draggable';

const App = () => (
  <ThemeProvider>
    <DraggableList>
      <DraggableList.Item>
        <Draggable>
          <Draggable.Grip />
          <Draggable.Content>Petunia</Draggable.Content>
        </Draggable>
      </DraggableList.Item>
      <DraggableList.Item>
        <Draggable>
          <Draggable.Grip />
          <Draggable.Content>Chrysanthemum</Draggable.Content>
        </Draggable>
      </DraggableList.Item>
      <DraggableList.Item>
        <Draggable>
          <Draggable.Grip />
          <Draggable.Content>Dahlia</Draggable.Content>
        </Draggable>
      </DraggableList.Item>
    </DraggableList>
  </ThemeProvider>
);

Horizontal layout

Set isHorizontal to flow list items inline rather than stacked.
<DraggableList isHorizontal>
  <DraggableList.Item>
    <Draggable isCompact>
      <Draggable.Content>Item A</Draggable.Content>
    </Draggable>
  </DraggableList.Item>
  <DraggableList.Item>
    <Draggable isCompact>
      <Draggable.Content>Item B</Draggable.Content>
    </Draggable>
  </DraggableList.Item>
</DraggableList>

Props

isHorizontal
boolean
Flows list items inline (horizontal layout) instead of stacked.

DraggableList.DropIndicator

DraggableList.DropIndicator renders a visual line between list items to indicate where a dragged item will be dropped. Place it as a sibling to DraggableList.Item elements. It inherits the list’s isHorizontal orientation from context.
import React, { Fragment } from 'react';
import { Draggable, DraggableList } from '@zendeskgarden/react-draggable';

const SortableList = ({ items, indicatorIndex }: { items: string[]; indicatorIndex: number }) => (
  <DraggableList>
    {indicatorIndex === 0 && (
      <DraggableList.DropIndicator aria-label="Drop indicator" />
    )}
    {items.map((item, idx) => (
      <Fragment key={item}>
        {indicatorIndex === idx && idx > 0 && (
          <DraggableList.DropIndicator aria-label="Drop indicator" />
        )}
        <DraggableList.Item>
          <Draggable>
            <Draggable.Grip />
            <Draggable.Content>{item}</Draggable.Content>
          </Draggable>
        </DraggableList.Item>
      </Fragment>
    ))}
    {indicatorIndex === items.length && (
      <DraggableList.DropIndicator aria-label="Drop indicator" />
    )}
  </DraggableList>
);
DraggableList.DropIndicator auto-generates an aria-label of “Drop indicator”. Override it with the aria-label prop when your context requires a more specific label.

Dropzone

Dropzone renders a droppable target area — for example, a trash zone for deleting items or a lane in a kanban board. Use Dropzone.Message for descriptive text and Dropzone.Icon for an icon. When isDanger is set and Dropzone.Message is present but no Dropzone.Icon is provided, a trash icon is rendered automatically.
import { ThemeProvider } from '@zendeskgarden/react-theming';
import { Dropzone } from '@zendeskgarden/react-draggable';

const App = () => (
  <ThemeProvider>
    <Dropzone>
      <Dropzone.Message>Drag items here</Dropzone.Message>
    </Dropzone>
  </ThemeProvider>
);

Active and highlighted states

<Dropzone isActive>
  <Dropzone.Message>Drop to add</Dropzone.Message>
</Dropzone>

<Dropzone isActive isHighlighted>
  <Dropzone.Message>Release to drop</Dropzone.Message>
</Dropzone>

Danger zone

<Dropzone isDanger isActive>
  <Dropzone.Message>Drop to delete</Dropzone.Message>
</Dropzone>

With icon

import ReplaceIcon from '@zendeskgarden/svg-icons/src/16/arrow-retweet-stroke.svg';

<Dropzone>
  <Dropzone.Icon>
    <ReplaceIcon />
  </Dropzone.Icon>
  <Dropzone.Message>Drop to replace</Dropzone.Message>
</Dropzone>

Props

isActive
boolean
Indicates a drop is possible — apply this while a draggable hovers over a valid drop area.
isHighlighted
boolean
Indicates a draggable is directly over the droppable area (ready to drop).
isDanger
boolean
Applies danger (destructive) styling. A trash icon is added automatically when Dropzone.Message is present and no Dropzone.Icon is provided.
isDisabled
boolean
Disables the dropzone.
isVertical
boolean
Aligns dropzone message content vertically.
tag
any
default:"'div'"
Updates the rendered HTML element.

Full example

The following example shows a sortable list with a drop indicator. Wire up onDragStart, onDragOver, and onDrop events using your chosen drag-and-drop library.
import React, { Fragment, useState } from 'react';
import { ThemeProvider } from '@zendeskgarden/react-theming';
import { Draggable, DraggableList, Dropzone } from '@zendeskgarden/react-draggable';

const INITIAL_ITEMS = ['Petunia', 'Chrysanthemum', 'Dahlia', 'Lavender'];

const App = () => {
  const [items, setItems] = useState(INITIAL_ITEMS);
  const [dragIndex, setDragIndex] = useState<number | null>(null);
  const [indicatorIndex, setIndicatorIndex] = useState<number | null>(null);

  return (
    <ThemeProvider>
      <DraggableList>
        {items.map((item, idx) => (
          <Fragment key={item}>
            {indicatorIndex === idx && (
              <DraggableList.DropIndicator aria-label="Drop indicator" />
            )}
            <DraggableList.Item>
              <Draggable
                isGrabbed={dragIndex === idx}
                draggable
                onDragStart={() => setDragIndex(idx)}
                onDragEnd={() => {
                  setDragIndex(null);
                  setIndicatorIndex(null);
                }}
              >
                <Draggable.Grip />
                <Draggable.Content>{item}</Draggable.Content>
              </Draggable>
            </DraggableList.Item>
          </Fragment>
        ))}
        {indicatorIndex === items.length && (
          <DraggableList.DropIndicator aria-label="Drop indicator" />
        )}
      </DraggableList>

      <Dropzone isActive={dragIndex !== null}>
        <Dropzone.Message>Drop to remove</Dropzone.Message>
      </Dropzone>
    </ThemeProvider>
  );
};

Build docs developers (and LLMs) love