Skip to main content

React

DragSelect has official React bindings: @dragselect/react.

npm i dragselect @dragselect/react

1. Wrap your component tree​

Everything inside a DragSelectProvider shares one DragSelect instance. It takes any of the settings. They are read once on mount; to change them later, call useDragSelect()?.setSettings({...}).

import { DragSelectProvider } from "@dragselect/react";

export const Gallery = ({ photos }) => (
<DragSelectProvider settings={{ selectedClass: "is-selected" }}>
{photos.map((photo) => (
<Photo key={photo.id} photo={photo} />
))}
<Toolbar />
</DragSelectProvider>
);

2. Make components selectable​

useSelectable returns a ref. It registers the element while the component is mounted and removes it on unmount. Put whatever you need to identify the item on the element itself, e.g. a data-id attribute.

import { useSelectable } from "@dragselect/react";

const Photo = ({ photo }) => {
const ref = useSelectable();
return <img ref={ref} src={photo.url} data-id={photo.id} />;
};

Selected elements get the ds-selected class (or your selectedClass), so style them with CSS. Selecting does not re-render your items.

3. Read the selection​

useSelection returns the selected elements and re-renders the component that calls it whenever the selection changes:

import { useSelection } from "@dragselect/react";

const Toolbar = () => {
const selectedIds = useSelection<HTMLImageElement>().map((el) => el.dataset.id);
return <span>{selectedIds.length} selected</span>;
};

Events​

useDragSelectEvent subscribes to any event and unsubscribes on unmount. It always calls the latest callback, so you don't need useCallback.

import { useDragSelectEvent } from "@dragselect/react";

useDragSelectEvent("DS:end", ({ items, isDragging }) => {
console.log(isDragging ? "moved" : "selected", items);
});

Area and drop zones​

import {
useDragSelectArea,
useDragSelectEvent,
useDropZone,
useSelection,
} from "@dragselect/react";

const Board = ({ onDelete, children }) => {
const areaRef = useDragSelectArea(); // only select inside this element
const trashRef = useDropZone("trash");
const selectedIds = useSelection().map((el) => el.dataset.id);

useDragSelectEvent("DS:end", ({ isDragging, dropTarget }) => {
if (isDragging && dropTarget?.id === "trash") onDelete(selectedIds);
});

return (
<div ref={areaRef}>
{children}
<div ref={trashRef}>🗑 Trash</div>
</div>
);
};

Drop zones​

useDropZone(id) registers a drop zone. The id is how DragSelect names the zone: it comes back as dropTarget.id in events, works with ds.getItemsDroppedByZoneId(id), and suffixes the classes items get (e.g. ds-dropped-target-archive).

import { useDragSelectEvent, useDropZone } from "@dragselect/react";

const Folder = ({ id, name, onMove }) => {
const ref = useDropZone(id);

// `items` is everything that was dragged: one element or the whole selection
useDragSelectEvent("DS:end", ({ items, isDragging, dropTarget }) => {
if (!isDragging || dropTarget?.id !== id) return;
onMove(items.map((el) => el.dataset.id));
});

return <div ref={ref}>📁 {name}</div>;
};

// used as:
<Folder id="archive" name="Archive" onMove={(ids) => archive(ids)} />;
/* the zone while a drag is in progress */
.ds-dropzone-ready { outline: 2px dashed; }
/* items that were dropped into the "archive" zone */
.ds-dropped-target-archive { opacity: 0.5; }

Anything else​

useDragSelect() returns the underlying instance, so every method is still available:

import { useDragSelect } from "@dragselect/react";

const ClearButton = () => {
const ds = useDragSelect(); // undefined on the first render
return <button onClick={() => ds?.clearSelection()}>Clear</button>;
};

API overview​

ExportReturnsPurpose
<DragSelectProvider settings?>Creates and tears down the instance.
useSelectable()refRegisters a selectable element.
useSelection<E>()E[]The selected elements.
useDragSelectEvent(name, cb)Subscribes to an event.
useDragSelectArea()refSets the drag area.
useDropZone(id)refRegisters a drop zone.
useDragSelect()DragSelect \| undefinedThe raw instance.

Works with React 18+, StrictMode, SSR, and the Next.js App Router. The bundle is marked 'use client'.

You can see it in use on this site's demo window.