The draggable Attribute
The draggable Attribute
Section titled “The draggable Attribute”The draggable attribute specifies whether an element can be dragged using native browser drag-and-drop functionality. Combined with the Drag and Drop API, it enables building intuitive drag-and-drop interfaces for sorting, uploading files, and moving content.
Syntax
Section titled “Syntax”<element draggable="value">Content</element>Values
Section titled “Values”| Value | Description |
|---|---|
true | Element can be dragged |
false | Element cannot be dragged (default for most elements) |
auto | Browser determines if element is draggable (default for images and links) |
Examples
Section titled “Examples”Basic Drag and Drop
Section titled “Basic Drag and Drop” Result
Sortable List
Section titled “Sortable List” Result
Drag and Drop File Upload
Section titled “Drag and Drop File Upload” Result
Kanban Board
Section titled “Kanban Board” Result
Drag and Drop Events
Section titled “Drag and Drop Events”Event Lifecycle
Section titled “Event Lifecycle”The drag-and-drop operation involves these events:
| Event | Target | Description |
|---|---|---|
dragstart | Dragged element | User starts dragging |
drag | Dragged element | Element is being dragged |
dragenter | Drop target | Dragged element enters drop zone |
dragover | Drop target | Dragged element is over drop zone |
dragleave | Drop target | Dragged element leaves drop zone |
drop | Drop target | Element is dropped |
dragend | Dragged element | Drag operation ends |
Complete Event Example
Section titled “Complete Event Example”// On the draggable elementelement.addEventListener('dragstart', (e) => { // Set data to transfer e.dataTransfer.setData('text/plain', element.id); e.dataTransfer.effectAllowed = 'move';
// Optional: Set custom drag image const img = new Image(); img.src = 'drag-icon.png'; e.dataTransfer.setDragImage(img, 0, 0);});
element.addEventListener('dragend', (e) => { // Clean up after drag element.classList.remove('dragging');});
// On the drop targetdropZone.addEventListener('dragover', (e) => { e.preventDefault(); // Required to allow drop e.dataTransfer.dropEffect = 'move';});
dropZone.addEventListener('drop', (e) => { e.preventDefault(); const id = e.dataTransfer.getData('text/plain'); const draggedElement = document.getElementById(id); dropZone.appendChild(draggedElement);});DataTransfer Object
Section titled “DataTransfer Object”The dataTransfer object stores drag data:
Setting Data
Section titled “Setting Data”element.addEventListener('dragstart', (e) => { // Set different data types e.dataTransfer.setData('text/plain', 'Plain text'); e.dataTransfer.setData('text/html', '<strong>HTML</strong>'); e.dataTransfer.setData('application/json', JSON.stringify({id: 123}));});Getting Data
Section titled “Getting Data”dropZone.addEventListener('drop', (e) => { const text = e.dataTransfer.getData('text/plain'); const html = e.dataTransfer.getData('text/html'); const json = JSON.parse(e.dataTransfer.getData('application/json'));});Effect Allowed
Section titled “Effect Allowed”e.dataTransfer.effectAllowed = 'move'; // Only movee.dataTransfer.effectAllowed = 'copy'; // Only copye.dataTransfer.effectAllowed = 'link'; // Only linke.dataTransfer.effectAllowed = 'all'; // Any effectDrop Effect
Section titled “Drop Effect”e.dataTransfer.dropEffect = 'move'; // Visual feedback for movee.dataTransfer.dropEffect = 'copy'; // Show copy cursore.dataTransfer.dropEffect = 'link'; // Show link cursore.dataTransfer.dropEffect = 'none'; // Show no-drop cursorBest Practices
Section titled “Best Practices”1. Provide Visual Feedback
Section titled “1. Provide Visual Feedback”Make drag operations obvious:
/* While dragging */.dragging { opacity: 0.5; transform: rotate(5deg); cursor: grabbing;}
/* Drag handle */.drag-handle { cursor: grab;}
/* Drop zone states */.dropzone { transition: all 0.2s;}
.dropzone.over { border-color: #3b82f6; background: #dbeafe;}2. Prevent Default Behavior
Section titled “2. Prevent Default Behavior”Always prevent default on dragover and drop:
dropZone.addEventListener('dragover', (e) => { e.preventDefault(); // Required!});
dropZone.addEventListener('drop', (e) => { e.preventDefault(); // Required!});3. Handle Touch Devices
Section titled “3. Handle Touch Devices”Drag and drop doesn’t work on touch devices by default:
// Use a polyfill or library// Or implement touch event handlerselement.addEventListener('touchstart', handleTouchStart);element.addEventListener('touchmove', handleTouchMove);element.addEventListener('touchend', handleTouchEnd);4. Make Drag Areas Clear
Section titled “4. Make Drag Areas Clear”Use visual cues for draggable elements:
<div class="card" draggable="true"> <span class="drag-handle">⋮⋮</span> <span class="content">Drag me</span></div>5. Add Keyboard Accessibility
Section titled “5. Add Keyboard Accessibility”Drag and drop is mouse-only; provide keyboard alternatives:
<div draggable="true"> Item 1 <button onclick="moveUp()">↑</button> <button onclick="moveDown()">↓</button></div>Common Patterns
Section titled “Common Patterns”Trash/Delete Zone
Section titled “Trash/Delete Zone”const trash = document.getElementById('trash');
trash.addEventListener('dragover', (e) => { e.preventDefault(); trash.classList.add('active');});
trash.addEventListener('drop', (e) => { e.preventDefault(); const id = e.dataTransfer.getData('text/plain'); document.getElementById(id).remove(); trash.classList.remove('active');});Clone vs Move
Section titled “Clone vs Move”element.addEventListener('dragstart', (e) => { // Hold Ctrl/Cmd to copy instead of move if (e.ctrlKey || e.metaKey) { e.dataTransfer.effectAllowed = 'copy'; } else { e.dataTransfer.effectAllowed = 'move'; }});Nested Drop Zones
Section titled “Nested Drop Zones”// Stop event propagation for nested zonesinnerZone.addEventListener('drop', (e) => { e.stopPropagation(); // Prevent parent from handling});Accessibility Considerations
Section titled “Accessibility Considerations”Not Keyboard Accessible
Section titled “Not Keyboard Accessible”ARIA Attributes
Section titled “ARIA Attributes”Add appropriate ARIA attributes:
<div draggable="true" role="button" aria-grabbed="false" tabindex="0"> Draggable item</div>Keyboard Alternatives
Section titled “Keyboard Alternatives”<div class="item" draggable="true"> <span>Task item</span> <button aria-label="Move up">↑</button> <button aria-label="Move down">↓</button></div>Screen Reader Announcements
Section titled “Screen Reader Announcements”// Announce changesconst announcement = document.getElementById('aria-live');announcement.textContent = 'Item moved to Done column';Common Pitfalls
Section titled “Common Pitfalls”Browser Support
Section titled “Browser Support”Excellent support across modern browsers:
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Basic drag and drop | 4+ | 3.5+ | 3.1+ | 12+ |
| DataTransfer API | All | All | All | All |
| File drag and drop | 7+ | 3.6+ | 6+ | 12+ |
Related Attributes
Section titled “Related Attributes”contenteditable- Editable content can be made draggabletabindex- Make non-interactive elements focusable for keyboard access
Libraries and Frameworks
Section titled “Libraries and Frameworks”For production apps, consider using libraries:
- SortableJS - Reorderable drag-and-drop lists
- Interact.js - Drag, drop, resize, and gestures
- React DnD - Drag and drop for React
- Vue.Draggable - Vue wrapper for SortableJS