controls
Define which areas can initiate dragging
The controls
plugin defines which parts of an element can be used to start dragging. Think modal headers, card handles, or toolbar grips - only specific areas should be draggable while the rest remains interactive.
controls({ allow: ControlFrom.selector('.handle') }); // Only handle can drag
controls({ block: ControlFrom.selector('.button') }); // Buttons can't drag
controls({
allow: ControlFrom.selector('.header'),
block: ControlFrom.selector('.close'),
}); // Header drags, close button doesn't
Basic Usage
import { useRef } from 'react';
import { controls, ControlFrom, useDraggable } from '@neodrag/react';
function ControlledDragging() {
const allowRef = useRef<HTMLDivElement>(null);
const blockRef = useRef<HTMLDivElement>(null);
useDraggable(allowRef, [
controls({ allow: ControlFrom.selector('.header') }),
]);
useDraggable(blockRef, [
controls({ block: ControlFrom.selector('button') }),
]);
return (
<div>
<div ref={allowRef}>
<div className="header">Drag handle</div>
<div className="content">Content (not draggable)</div>
</div>
<div ref={blockRef}>
<p>Drag anywhere in this area</p>
<button>Can't drag from here</button>
</div>
</div>
);
}
ControlFrom Utilities
Selector-Based Controls
import { useRef } from 'react';
import { controls, ControlFrom, useDraggable } from '@neodrag/react';
function SelectorControls() {
const singleRef = useRef<HTMLDivElement>(null);
const multiRef = useRef<HTMLDivElement>(null);
const blockRef = useRef<HTMLDivElement>(null);
useDraggable(singleRef, [
controls({ allow: ControlFrom.selector('.handle') }),
]);
useDraggable(multiRef, [
controls({
allow: ControlFrom.selector('.handle, .title, .grip'),
}),
]);
useDraggable(blockRef, [
controls({
block: ControlFrom.selector('button, input, select'),
}),
]);
return (
<div>
<div ref={singleRef}>
<div className="handle">Drag me</div>
<div>Not draggable</div>
</div>
<div ref={multiRef}>
<div className="title">Title bar</div>
<div className="handle">Handle</div>
<div className="grip">⋮⋮</div>
<div className="content">Content</div>
</div>
<div ref={blockRef}>
<p>Draggable area</p>
<button>Button (blocked)</button>
<input placeholder="Input (blocked)" />
</div>
</div>
);
}
Element-Based Controls
import { useRef } from 'react';
import { controls, ControlFrom, useDraggable } from '@neodrag/react';
function ElementControls() {
const containerRef = useRef<HTMLDivElement>(null);
const handleRef = useRef<HTMLDivElement>(null);
const blockContainerRef = useRef<HTMLDivElement>(null);
const blockRef = useRef<HTMLDivElement>(null);
useDraggable(containerRef, [
controls({
allow: () => ControlFrom.elements([handleRef.current!]),
}),
]);
useDraggable(blockContainerRef, [
controls({
block: () => ControlFrom.elements([blockRef.current!]),
}),
]);
return (
<div>
<div ref={containerRef}>
<div ref={handleRef}>Specific handle element</div>
<div>Not draggable</div>
</div>
<div ref={blockContainerRef}>
<p>Draggable area</p>
<div ref={blockRef}>Blocked element</div>
</div>
</div>
);
}
Advanced Control Logic
Priority Handling
import { useRef } from 'react';
import { controls, ControlFrom, useDraggable } from '@neodrag/react';
function PriorityControls() {
const allowWinsRef = useRef<HTMLDivElement>(null);
const blockWinsRef = useRef<HTMLDivElement>(null);
useDraggable(allowWinsRef, [
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'allow',
}),
]);
useDraggable(blockWinsRef, [
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'block',
}),
]);
return (
<div>
<div ref={allowWinsRef}>
<div className="drag-zone">
Drag zone
<div className="no-drag">Allow wins here</div>
</div>
</div>
<div ref={blockWinsRef}>
<div className="drag-zone">
Drag zone
<div className="no-drag">Block wins here</div>
</div>
</div>
</div>
);
}
Real-World Examples
Modal with Header Handle
import { useRef } from 'react';
import {
useDraggable,
controls,
ControlFrom,
bounds,
BoundsFrom,
} from '@neodrag/react';
function DraggableModal() {
const modalRef = useRef<HTMLDivElement>(null);
useDraggable(modalRef, [
controls({
allow: ControlFrom.selector('.modal-header'),
block: ControlFrom.selector('.modal-close, .modal-body button'),
}),
bounds(BoundsFrom.viewport()),
]);
return (
<div ref={modalRef}>
<div className="modal-header">
<h3>Modal Title</h3>
<button className="modal-close">×</button>
</div>
<div className="modal-body">
<p>Modal content</p>
<button>Action Button</button>
</div>
</div>
);
}
How It Works
The controls plugin manages drag initiation permissions:
- Finds control zones on setup by querying the DOM
- Checks click position in
shouldStart
hook - Determines permission based on which zones contain the click point
- Returns boolean to allow/prevent drag start
Zone priority logic:
- If
allow
zones exist and click is outside all of them → block - If click is in both allow and block zones → use
priority
option - Smaller (nested) zones take precedence over larger ones
API Reference
function controls(options?: {
allow?: ReturnType<
typeof ControlFrom.selector | typeof ControlFrom.elements
>;
block?: ReturnType<
typeof ControlFrom.selector | typeof ControlFrom.elements
>;
priority?: 'allow' | 'block';
}): Plugin;
Options:
allow
- Control zones that can initiate draggingblock
- Control zones that cannot initiate draggingpriority
- Which wins when zones overlap (‘allow’ | ‘block’)
ControlFrom utilities:
ControlFrom.selector(cssSelector)
- Elements matching CSS selectorControlFrom.elements(nodeList)
- Specific DOM elements
Returns: A plugin object for use with draggable.