Skip to content
SortableGuides

Handle and Acceptance

comins-sortable@0.1.6

Playground paths below are local routes for the module repository’s development server. They are not live routes on this documentation site.

Use selectors to control activation and callbacks to control destinations.

import { useState } from 'react';
import { SortableArea, SortableRoot } from 'comins-sortable/react';
type Item = { id: string; label: string };
export function HandleExample() {
const [items, setItems] = useState<Item[]>([
{ id: 'task-1', label: 'Plan' },
{ id: 'task-2', label: 'Build' },
]);
return (
<SortableRoot<Item>>
<SortableArea
areaId="tasks"
items={items}
itemKey="id"
handle=".drag-handle"
ignore="a, input, textarea, select"
activationDistance={4}
accept={(context) => context.source.areaId !== 'locked'}
onItemsChange={(next) => setItems([...next])}
>
{(item) => (
<div>
<button
type="button"
className="drag-handle"
aria-label={`Drag ${item.label}`}
>
⠿
</button>
<span className="card-label">{item.label}</span>
</div>
)}
</SortableArea>
</SortableRoot>
);
}
.drag-handle {
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
.card-label {
-webkit-user-select: text;
user-select: text;
}

An explicit handle takes precedence over ignore. Use a real button for an accessible handle and provide a descriptive label. activationDistance prevents small pointer movement from immediately starting a drag.

For cards with selectable text, start moves only from the handle. Set the body’s user-select and -webkit-user-select to text, and the handle’s to none. Apply touch-action: none to the handle to preserve default touch behavior in the body. The Playground uses this pattern by default and suppresses body selection only during an active drag. Selection becomes available again after completion or cancellation. With multiDrag, use modifier keys with handle clicks to select items. Input outside the handle or on ignored content does not change item selection.

Omit handle to start dragging anywhere on an item except default ignored inputs, buttons, links, editable elements, and other ignored targets. A selector such as handle=".card-title" restricts activation to that descendant. The Drag start areas example compares dedicated handle, title, and whole-card modes. Whole-card mode uses body drags for sorting; returning to handle mode restores body-text selection. Other examples retain their default handles.

Version 0.1.4 uses effective editability for the default exclusion: contenteditable="", "true", "plaintext-only", and inherited editable content are protected. A contenteditable="false" descendant is not excluded solely because its parent is editable. A custom ignore replaces the default exclusions, and an explicit matching handle still takes precedence.

Known Safari limitation (2026-09-22): In native Safari 26.6.2, the React Playground title-only mode can leave body text selected after reordering. It reproduces from a fresh page and is not fixed by switching back to handle mode. Dedicated handle mode remains the recommended example for selectable body text. Whole-card mode intentionally uses body drags for sorting. See the reproduction and verification scope. This observation does not establish the same failure in every adapter or browser.

A handle confines pointer activation; a labelled button does not add keyboard sorting. Active-drag text-selection suppression is the Playground’s styling and lifecycle choice, not a global user-select rule installed by the package. If an application needs it, scope it to that widget and release it in onAfterDrag and on unmount.

Core prevents native browser dragstart only while processing an accepted mouse sorting attempt, so an existing text selection cannot steal the gesture. It does not clear the selection range. After moving a card, users can select just the value they need and copy it. Body or link input that was not accepted as a sorting attempt is outside this guard.

accept(context) controls a candidate destination. Group put controls which source groups may enter. disabled blocks a source or destination area at runtime and cancels an active owner drag.

While the pointer is over a rejected destination or a non-sortable sibling in the same host, the target and drag preview expose data-comins-sortable-rejection. The default styles.css applies a red outline and a not-allowed cursor. Override them with --comins-sortable-rejection-outline and --comins-sortable-rejection-outline-offset. The state is removed when the pointer returns to a valid destination or the drag ends.

Playground:

  • /examples/handle/react
  • /examples/accept/react

Browser tests send native Chromium touch input for handle sorting, body scrolling, empty-list transfers, secondary pointers, cancellation and auto-scroll in all four adapters. Dedicated handles use touch-action: none; the body remains available for scrolling and text selection. Whole-card mode intentionally reserves its body for dragging. These tests are not physical Galaxy or native Safari results.


Runnable example · Source guide for this version