React Draggable - Getting Started

jQuery
NOTE
Before you start the tutorial, ensure DevExtreme is installed in your application.
Angular
NOTE
Before you start the tutorial, ensure DevExtreme is installed in your application.
Vue
NOTE
Before you start the tutorial, ensure DevExtreme is installed in your application.
React
NOTE
Before you start the tutorial, ensure DevExtreme is installed in your application.

This tutorial guides you through the following steps:

  • Add a Draggable to a page.
  • Configure Draggable options.
  • Handle events.

Each section in this tutorial covers a single configuration step. You can find the complete source code in the following GitHub repository:

View on GitHub

Create Draggable

jQuery

Add DevExtreme to your jQuery application and use the following code to create a Draggable:

index.js
index.html
$(function() {
    $("#draggable").dxDraggable({});
});
<html>
    <head>
        <!-- ... -->
        <script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
        <link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/25.2.4/css/dx.fluent.blue.light.css">
        <script type="text/javascript" src="https://cdn3.devexpress.com/jslib/25.2.4/js/dx.all.js"></script>
        <script type="text/javascript" src="index.js"></script>
    </head>
    <body class="dx-viewport">
        <div id="draggable"></div>
    </body>
</html>
Angular

Add DevExtreme to your Angular application and use the following code to create a Draggable:

app.component.html
app.component.ts
app.module.ts
angular.json
<dx-draggable></dx-draggable>
import { Component } from '@angular/core';
import { type DxDraggableTypes } from 'devextreme-angular/ui/draggable';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DxDraggableModule } from 'devextreme-angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        DxDraggableModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
{
    // ...
    "styles": [
        "node_modules/devextreme/dist/css/dx.fluent.blue.light.css",
    ],
}
Vue

Add DevExtreme to your Vue application and use the following code to create a Draggable:

App.vue
<template>
    <DxDraggable />
</template>

<script setup lang="ts">
import { DxDraggable, type DxDraggableTypes } from 'devextreme-vue/draggable';
import 'devextreme/dist/css/dx.fluent.blue.light.css';

</script>
React

Add DevExtreme to your React application and use the following code to create a Draggable:

App.tsx
import React, { JSX, useState } from 'react';
import { Draggable, type DraggableTypes } from 'devextreme-react/draggable';
import 'devextreme/dist/css/dx.fluent.blue.light.css';

function App(): JSX.Element {
    return (
        <Draggable />
    );
}

export default App;

Draggable does not include default visual elements. Specify custom markup as follows:

jQuery
index.js
index.html
$(function() {
    $("#note-1").dxDraggable({});
});
<html>
    <head>
        <!-- ... -->
    </head>
    <body class="dx-viewport">
        <div id="note-1" class="note">
            <div class="color-indicator blue"></div>
            <div class="text-container">
                <div class="body-text-box">Install New Router in Dev Room</div>
                <div class="detail-text-box">Amelia Harper</div>
            </div>
        </div>
    </body>
</html>

This example creates four Draggable components inside a common .board container:

index.js
index.html
$(function() {
    $("#note-1").dxDraggable({});
    $("#note-2").dxDraggable({});
    $("#note-3").dxDraggable({});
    $("#note-4").dxDraggable({});
});
<div class="board">
    <div id="note-1" class="note">
        <div class="color-indicator blue"></div>
        <div class="text-container">
            <div class="body-text-box">Install New Router in Dev Room</div>
            <div class="detail-text-box">Amelia Harper</div>
        </div>
    </div>

    <div id="note-2" class="note">
        <div class="color-indicator green"></div>
        <div class="text-container">
            <div class="body-text-box">👨‍💻 Launch New Website</div>
            <div class="detail-text-box">Brett Wade</div>
        </div>
    </div>

    <div id="note-3" class="note">
        <div class="color-indicator red"></div>
        <div class="text-container">
            <div class="body-text-box">Prepare 2026 Marketing Plan</div>
            <div class="detail-text-box">Robert Reagan</div>
        </div>
    </div>

    <div id="note-4" class="note">
        <div class="color-indicator yellow"></div>
        <div class="text-container">
            <div class="body-text-box">🖥️ Approve Personal Computer Upgrade Plan</div>
            <div class="detail-text-box">Bart Arnaz</div>
        </div>
    </div>
</div>
Angular
app.component.html
<dx-draggable id="note-1" class="note">
    <div class="color-indicator"></div>
    <div class="text-container">
        <div class="body-text-box">Install New Router in Dev Room</div>
        <div class="detail-text-box">Amelia Harper</div>
    </div>
</dx-draggable>

This example defines Draggable markup within a custom component, note.

This component configures the NoteInfo interface to specify note information. Use this interface within an array to define four notes. Implement the note component within a @for directive to add these notes to the app:

note.component.html
note.component.ts
app.component.html
app.component.ts
<div>
    <dx-draggable
        [id]="id()"
        [group]="group()"
        [boundary]="boundary()"
        class="note"
    >
        <div class="color-indicator"></div>
        <div class="text-container">
            <div class="body-text-box">{{ task() }}</div>
            <div class="detail-text-box">{{ assignee() }}</div>
        </div>
    </dx-draggable>
</div>
import { Component, input, model } from '@angular/core';

export interface NoteInfo {
    id: string;
    task: string;
    assignee: string;
}

@Component({
    selector: 'note',
    templateUrl: './note.component.html',
    styleUrls: ['../app/app.component.scss'],
})
export class NoteComponent {
    id = input<string>();
    group = input<string>('notes');
    boundary = input<string>('.board')
    task = input<string>();
    assignee = input<string>();
    // ...

}
<div class="boundary-text">Dragging Boundary</div>
<div class="board">
    @for (note of notes; track note) {
    <note
        [id]="note.id"
        [task]="note.task"
        [assignee]="note.assignee"
    ></note>
    }
</div>
<div class="boundary-text">Dragging Boundary</div>
import { Component } from '@angular/core';
import { type NoteInfo } from 'src/note/note.component';

// ...
export class AppComponent {
    notes: NoteInfo[] = [{
        id: 'note-1',
        task: 'Install New Router in Dev Room',
        assignee: 'Amelia Harper',
    }, {
        id: 'note-2',
        task: '👨‍💻 Launch New Website',
        assignee: 'Brett Wade',
    }, {
        id: 'note-3',
        task: 'Prepare 2026 Marketing Plan',
        assignee: 'Robert Reagan',
    }, {
        id: 'note-4',
        task: '🖥️ Approve Personal Computer Upgrade Plan',
        assignee: 'Bart Arnaz',
    }];
}
Vue
App.vue
<template>
    <DxDraggable id="note-1">
        <div class="note">
            <div class="color-indicator"></div>
            <div class="text-container">
                <div class="body-text-box">Install New Router in Dev Room</div>
                <div class="detail-text-box">Amelia Harper</div>
            </div>
        </div>
    </DxDraggable>
</template>

This example defines Draggable markup within a custom component, Note.

This component configures two interfaces:

  • export interface NoteInfo: Specifies note information.
  • interface NoteProps: Defines properties of the Note component.

Create an array of NoteInfo objects to define four notes. Implement the Note component within a <template> tag and use the v-for directive to add these notes to the app:

Note.vue
App.vue
<template>
    <DxDraggable
        :id="id"
        :group="group"
        :boundary="boundary"
    >
        <div class="note">
            <div class="color-indicator"></div>
            <div class="text-container">
                <div class="body-text-box">{{ task }}</div>
                <div class="detail-text-box">{{ assignee }}</div>
            </div>
        </div>
    </DxDraggable>
</template>

<script setup lang="ts">
// ...

export interface NoteInfo {
    id: string;
    task: string;
    assignee: string;
}

interface NoteProps extends NoteInfo {
    group?: string;
    boundary?: string;
    // ...
}

const {
    id,
    group = 'notes',
    boundary = '.board',
    task,
    assignee,
    // ...
} = defineProps<NoteProps>();

</script>
<template>
<div class="boundary-text">Dragging Boundary</div>
<div class="board">
    <template v-for="note in notes">
        <Note
            :id="note.id"
            :task="note.task"
            :assignee="note.assignee"
        />
    </template>
</div>
<div class="boundary-text">Dragging Boundary</div>
</template>

<script setup lang="ts">
// ...

import Note, { type NoteInfo } from "./Note.vue";

const notes: NoteInfo[] = [
    {
        id: 'note-1',
        task: 'Install New Router in Dev Room',
        assignee: 'Amelia Harper',
    },
    {
        id: 'note-2',
        task: '👨‍💻 Launch New Website',
        assignee: 'Brett Wade',
    },
    {
        id: 'note-3',
        task: 'Prepare 2026 Marketing Plan',
        assignee: 'Robert Reagan',
    },
    {
        id: 'note-4',
        task: '🖥️ Approve Personal Computer Upgrade Plan',
        assignee: 'Bart Arnaz',
    },
];

</script>
React
App.tsx
import { Draggable } from 'devextreme-react/draggable';

function App(): JSX.Element {
    return (
        <Draggable id="note-1" className="note">
            <div className="color-indicator blue"></div>
            <div className="text-container">
                <div className="body-text-box">Install New Router in Dev Room</div>
                <div className="detail-text-box">Amelia Harper</div>
            </div>
        </Draggable>
    );
}

This example defines Draggable markup within a custom component, Note.

This component configures two interfaces:

  • export interface NoteInfo: Specifies note information.
  • interface NoteProps: Defines properties of the Note component.

Create an array of NoteInfo objects to define four notes. Implement the Note component within an Array.map() callback to add these notes to the app:

Note.tsx
Note.types.tsx
App.tsx
import { Draggable, type DraggableTypes } from 'devextreme-react/draggable';
import { type NoteProps } from './Note.types';
// ...

function Note({
    id,
    group = 'notes',
    boundary = '.board',
    task,
    assignee,
    // ...
}: NoteProps) {
    return (
        <Draggable
            id={id}
            group={group}
            boundary={boundary}
        >
            <div
                className={`note ${isOverlapped ? 'overlapped' : ''}`}
            >
                <div className="color-indicator"></div>
                <div className="text-container">
                    <div className="body-text-box">{task}</div>
                    <div className="detail-text-box">{assignee}</div>
                </div>
            </div>
        </Draggable>
    );
}

export default Note;
export interface NoteInfo {
    id: string;
    task: string;
    assignee: string;
}

export interface NoteProps extends NoteInfo {
    group?: string;
    boundary?: string;
    // ...
}
import Note from './components/Note/Note.tsx';
import { type NoteInfo } from './components/Note/Note.types.tsx';
// ...

const notes: NoteInfo[] = [{
    id: 'note-1',
    task: 'Install New Router in Dev Room',
    assignee: 'Amelia Harper',
}, {
    id: 'note-2',
    task: '👨‍💻 Launch New Website',
    assignee: 'Brett Wade',
}, {
    id: 'note-3',
    task: 'Prepare 2026 Marketing Plan',
    assignee: 'Robert Reagan',
}, {
    id: 'note-4',
    task: '🖥️ Approve Personal Computer Upgrade Plan',
    assignee: 'Bart Arnaz',
}];

function App(): JSX.Element {
    return (
        <div className="boundary-text">Dragging Boundary</div>
        <div className="board">
            {notes.map((note) => (
                <Note
                    {...note}
                    key={note.id}
                />
            ))}
        </div>
        <div className="boundary-text">Dragging Boundary</div>
    );
}

Configure Draggable Options

This example specifies the following Draggable properties:

  • boundary: Constrains Draggable movement inside a container (.board).
  • group: Specifies an interaction group (used in events such as onDragMove).

Handle Events

jQuery

This example specifies an onDragStart handler to update the note's Z-index (bring to front):

index.js
$(function() {
    let z = 1;

    $("#note-1").dxDraggable({
        onDragStart(e) {
            changeZIndex($(e.element[0]));
        }
        // ...
    });

    function changeZIndex(el) {
        el.css("z-index", z);
        z++;
    }
});

This example also specifies handlers for four common events (not specific to Draggable):

  • click
  • dxdragenter
  • dxdragleave
  • dxdrop

To attach handlers to common events, call Draggable.on():

index.js
index.css
$(function() {
    $("#note-1").dxDraggable({
        group: 'notes',

        // ...
    }).on({
        "click": handleClick,
        "dxdragenter": handleDragEnter,
        "dxdragleave": handleDragStop,
        "dxdrop": handleDragStop,
    });

    // ...

    function handleClick(e) {
        changeZIndex($(e.currentTarget));
    }

    function handleDragEnter(e) {
        e.target.classList.add('overlapped');
    }

    function handleDragStop(e) {
        e.target.classList.remove('overlapped');
    }
});
.note.overlapped {
    outline: 1px dashed var(--dx-color-primary);
}
NOTE
Specify an identical group value for all Draggable instances to ensure the components interact with each other (in dxdragenter, dxdragleave, and dxdrop handlers).
Angular

This example specifies the following event handlers to update the note's Z-index (bring to front while dragging):

This example also implements a (click) handler on the parent <div> element within the note component:

note.component.html
note.component.ts
app.component.html
app.component.ts
app.component.scss
<div (click)="handleClick()">
    <dx-draggable ...
        [style.z-index]="currentZIndex"
        [class.overlapped]="isOverlapped()"
        (onDragMove)="handleDragMove($event)"
        (onDragEnd)="handleDragEnd()"
        (onDragStart)="handleDragStart()"
    >
        <!-- ... -->
    </dx-draggable>
</div>
import { type DxDraggableTypes } from 'devextreme-angular/ui/draggable';

// ...
export class NoteComponent {
    // ...

    overlappedComponentId: string | null = null;
    currentZIndex: number = 0;

    updateZIndex(): void {
        const updatedZIndex = this.zIndex() + 1;
        this.currentZIndex = updatedZIndex;
        this.zIndex.update(() => updatedZIndex);

        console.log(this.currentZIndex)
    }

    handleDragStart(): void {
        this.updateZIndex();
    }

    handleDragMove(e: DxDraggableTypes.DragMoveEvent): void {
        if (e.toComponent !== e.component) {
            const toComponentId = e.toComponent.element().id;
            this.startOverlap()(toComponentId);
            this.overlappedComponentId = toComponentId;
        } else {
            this.stopOverlap()();
            this.overlappedComponentId = null;
        }
    }

    handleDragEnd(): void {
        this.stopOverlap()();
    }

    handleClick(): void {
        this.updateZIndex();
    }
}
<div class="boundary-text">Dragging Boundary</div>
    <div class="board">
        @for (note of notes; track note) {
        <note ...
            [isOverlapped]="overlappedId === note.id"
            [(zIndex)]="zIndex"
            [startOverlap]="startOverlap"
            [stopOverlap]="stopOverlap"
        ></note>
        }
    </div>
<div class="boundary-text">Dragging Boundary</div>
import { Component } from '@angular/core';

// ...
export class AppComponent {
    // ...

    zIndex: number = 0;
    overlappedId: string | null = null;

    startOverlap: (id: string) => void = (id) => {
        this.overlappedId = id;
    }

    stopOverlap: () => void = () => {
        this.overlappedId = null;
    }

    handleDragEnd: () => void = () => {
        this.stopOverlap();
    }
}
.note.overlapped {
    outline: 1px dashed var(--dx-color-primary);
}
Vue

This example specifies the following event handlers to update the note's Z-index (bring to front while dragging):

This example also implements a @click handler on the parent <div> element within the Note component:

Note.vue
App.vue
<template>
    <DxDraggable ...
        :style="{zIndex: currentZIndex}"
        @drag-start="handleDragStart"
        @drag-move="handleDragMove"
        @drag-end="handleDragEnd"
    >
        <div
            :class="`note ${isOverlapped ? 'overlapped' : ''}`"
            @click="handleClick"
        >
            <!-- ... -->
        </div>
    </DxDraggable>
</template>

<script setup lang="ts">
import { ref } from "vue";
// ...

const overlappedComponentId = ref<string | null>(null);
const currentZIndex = ref<number>(0);

function updateZIndex() {
    const zIndexToUpdate = zIndex.value + 1;
    currentZIndex.value = zIndexToUpdate;
    zIndex.value = zIndexToUpdate;
}

function handleDragStart() {
    updateZIndex();
}

function handleDragMove(e: DxDraggableTypes.DragMoveEvent) {
    if (e.toComponent !== e.component) {
        const toComponentId = e.toComponent.element().id;
        startOverlap(toComponentId);
        overlappedComponentId.value = toComponentId;
    } else {
        stopOverlap();
        overlappedComponentId.value = null;
    }
}

function handleDragEnd() {
    stopOverlap();
}

function handleClick() {
    updateZIndex();
}
</script>
<template>
    <div class="boundary-text">Dragging Boundary</div>
    <div class="board">
        <template v-for="note in notes">
        <Note ...
            :is-overlapped="overlappedId === note.id"
            v-model:z-index="zIndex"
            :start-overlap="startOverlap"
            :stop-overlap="stopOverlap"
        />
        </template>
    </div>
    <div class="boundary-text">Dragging Boundary</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
// ...

const overlappedId = ref<string | null>(null);
const zIndex = ref<number>(0);

function startOverlap(id: string) {
    overlappedId.value = id;
}

function stopOverlap() {
    overlappedId.value = null;
}
</script>

<style scoped>
.note.overlapped {
    outline: 1px dashed var(--dx-color-primary);
}

</style>
React

This example specifies the following event handlers to update the note's Z-index (bring to front while dragging):

This example also implements an onClick handler on the parent <div> element within the Note component:

Note.tsx
App.tsx
index.css
import React, {
    useCallback, useMemo, useRef, useState,
} from 'react';
// ...

function Note({ ... }: NoteProps) {
    const overlappedComponentId = useRef<string | null>(null);
    const [currentZIndex, setCurrentZIndex] = useState(0);

    const style = useMemo(() => ({ zIndex: currentZIndex }), [currentZIndex]);

    const updateZIndex = useCallback(() => {
        const zIndexToUpdate = zIndex.current + 1;
        setCurrentZIndex(zIndexToUpdate);
        zIndex.current = zIndexToUpdate;
    }, []);

    const handleDragStart = useCallback(() => {
        updateZIndex();
    }, []);

    const handleDragMove = useCallback((e: DraggableTypes.DragMoveEvent) => {
        if (e.toComponent !== e.component) {
            const toComponentId = e.toComponent.element().id;
            startOverlap(toComponentId);
            overlappedComponentId.current = toComponentId;
        } else {
            stopOverlap();
            overlappedComponentId.current = null;
        }
    }, []);

    const onClick = useCallback(() => {
        updateZIndex();
    }, [updateZIndex]);

    const handleDragEnd = useCallback(() => {
        stopOverlap();
    }, []);

    return (
        <Draggable ...
            style={style}
            onDragStart={handleDragStart}
            onDragMove={handleDragMove}
            onDragEnd={handleDragEnd}
        >
        <div
            className={`note ${isOverlapped ? 'overlapped' : ''}`}
            onClick={onClick}
        >
            {/* ... */}
        </div>
        </Draggable>
    );
}
import { useCallback, useRef, useState } from 'react';
// ...

function App(): JSX.Element {
    const zIndex = useRef(0);
    const [overlappedId, setOverlappedId] = useState<string | null>(null);

    const startOverlap = useCallback((id: string) => {
        setOverlappedId(id);
    }, []);

    const stopOverlap = useCallback(() => {
        setOverlappedId(null);
    }, []);

    return (
        <div className="demo-container dx-theme-fluent-typography">
        <div className="boundary-text">Dragging Boundary</div>
        <div className="board">
            {notes.map((note) => (
                <Note
                    {...note}
                    key={note.id}
                    isOverlapped={overlappedId === note.id}
                    zIndex={zIndex}
                    startOverlap={startOverlap}
                    stopOverlap={stopOverlap}
                />
            ))}
        </div>
        <div className="boundary-text">Dragging Boundary</div>
        </div>
    );
}
.note.overlapped {
    outline: 1px dashed var(--dx-color-primary);
}

Refer to the following repository for the complete source code of this tutorial:

View on GitHub