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
Grabbed
Placeholder
Disabled
Compact
Bare
<Draggable isGrabbed>
<Draggable.Grip />
<Draggable.Content>Currently dragging</Draggable.Content>
</Draggable>
<Draggable isPlaceholder>
<Draggable.Grip />
<Draggable.Content>Original position</Draggable.Content>
</Draggable>
<Draggable isDisabled>
<Draggable.Grip />
<Draggable.Content>Cannot be dragged</Draggable.Content>
</Draggable>
<Draggable isCompact>
<Draggable.Grip />
<Draggable.Content>Compact item</Draggable.Content>
</Draggable>
<Draggable isBare>
<Draggable.Grip />
<Draggable.Content>No border</Draggable.Content>
</Draggable>
Props
Applies grab styling to indicate the item is currently being dragged.
Hides content and applies placeholder background styling to represent the original position during a drag.
Disables the draggable and applies aria-disabled.
Applies compact (reduced) spacing.
Removes borders from the draggable item.
Applies an inset box-shadow on focus instead of the default outset ring.
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
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
Indicates a drop is possible — apply this while a draggable hovers over a valid drop area.
Indicates a draggable is directly over the droppable area (ready to drop).
Applies danger (destructive) styling. A trash icon is added automatically when Dropzone.Message is present and no Dropzone.Icon is provided.
Aligns dropzone message content vertically.
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>
);
};