0
# UI Interactions
1
2
User interface enhancements including drag-and-drop sorting and focus management for improved accessibility and user experience.
3
4
## Capabilities
5
6
### useSortable
7
8
Drag-and-drop sorting functionality using SortableJS with reactive list updates.
9
10
```typescript { .api }
11
/**
12
* Drag-and-drop sorting with reactive list updates
13
* @param selector - CSS selector for sortable container
14
* @param list - Reactive array to keep in sync with DOM order
15
* @param options - SortableJS and VueUse configuration options
16
* @returns Sortable control interface
17
*/
18
function useSortable<T>(
19
selector: string,
20
list: MaybeRef<T[]>,
21
options?: UseSortableOptions
22
): UseSortableReturn;
23
24
/**
25
* Drag-and-drop sorting with element reference
26
* @param el - Element or element getter function
27
* @param list - Reactive array to keep in sync with DOM order
28
* @param options - SortableJS and VueUse configuration options
29
* @returns Sortable control interface
30
*/
31
function useSortable<T>(
32
el: MaybeRefOrGetter<MaybeElement>,
33
list: MaybeRef<T[]>,
34
options?: UseSortableOptions
35
): UseSortableReturn;
36
37
interface UseSortableReturn {
38
/** Start the sortable functionality */
39
start: () => void;
40
/** Stop the sortable functionality */
41
stop: () => void;
42
/** Get or set SortableJS options */
43
option: (<K extends keyof Sortable.Options>(name: K, value: Sortable.Options[K]) => void)
44
& (<K extends keyof Sortable.Options>(name: K) => Sortable.Options[K]);
45
}
46
47
type UseSortableOptions = Sortable.Options & ConfigurableDocument;
48
49
// SortableJS Options (key ones)
50
namespace Sortable {
51
interface Options {
52
group?: string | GroupOptions;
53
sort?: boolean;
54
disabled?: boolean;
55
animation?: number;
56
handle?: string;
57
filter?: string;
58
draggable?: string;
59
ghostClass?: string;
60
chosenClass?: string;
61
dragClass?: string;
62
direction?: 'vertical' | 'horizontal';
63
touchStartThreshold?: number;
64
emptyInsertThreshold?: number;
65
onStart?: (evt: SortableEvent) => void;
66
onEnd?: (evt: SortableEvent) => void;
67
onAdd?: (evt: SortableEvent) => void;
68
onUpdate?: (evt: SortableEvent) => void;
69
onSort?: (evt: SortableEvent) => void;
70
onRemove?: (evt: SortableEvent) => void;
71
onMove?: (evt: MoveEvent) => boolean | -1 | 1 | void;
72
onClone?: (evt: SortableEvent) => void;
73
onChange?: (evt: SortableEvent) => void;
74
}
75
76
interface SortableEvent {
77
oldIndex?: number;
78
newIndex?: number;
79
item: HTMLElement;
80
from: HTMLElement;
81
to: HTMLElement;
82
clone: HTMLElement;
83
}
84
}
85
```
86
87
**Usage Examples:**
88
89
```typescript
90
import { useSortable } from "@vueuse/integrations/useSortable";
91
import { ref } from 'vue';
92
93
// Basic sortable list
94
const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4']);
95
96
const { start, stop } = useSortable('.sortable-list', items, {
97
animation: 150,
98
ghostClass: 'ghost',
99
onEnd: (evt) => {
100
console.log('Item moved from', evt.oldIndex, 'to', evt.newIndex);
101
}
102
});
103
104
// With element reference
105
const sortableEl = ref<HTMLElement>();
106
const { start, stop } = useSortable(sortableEl, items);
107
108
// Advanced configuration
109
const todoItems = ref([
110
{ id: 1, text: 'Learn Vue.js', completed: false },
111
{ id: 2, text: 'Build an app', completed: false },
112
{ id: 3, text: 'Deploy to production', completed: true }
113
]);
114
115
const { start, stop, option } = useSortable('.todo-list', todoItems, {
116
handle: '.drag-handle',
117
filter: '.no-drag',
118
animation: 200,
119
ghostClass: 'sortable-ghost',
120
chosenClass: 'sortable-chosen',
121
dragClass: 'sortable-drag',
122
onStart: (evt) => {
123
console.log('Drag started');
124
},
125
onEnd: (evt) => {
126
console.log('Drag ended');
127
// List is automatically updated
128
}
129
});
130
131
// Dynamic options
132
option('animation', 300);
133
const currentAnimation = option('animation');
134
135
// Multiple lists with shared group
136
const list1 = ref(['A', 'B', 'C']);
137
const list2 = ref(['X', 'Y', 'Z']);
138
139
useSortable('.list-1', list1, {
140
group: 'shared',
141
animation: 150
142
});
143
144
useSortable('.list-2', list2, {
145
group: 'shared',
146
animation: 150
147
});
148
```
149
150
### Utility Functions
151
152
Helper functions for DOM manipulation in sortable contexts.
153
154
```typescript { .api }
155
/**
156
* Insert a DOM node at a specific index within a parent element
157
* @param parentElement - Parent container element
158
* @param element - Element to insert
159
* @param index - Target index position
160
*/
161
function insertNodeAt(parentElement: Element, element: Element, index: number): void;
162
163
/**
164
* Remove a DOM node from its parent
165
* @param node - Node to remove
166
*/
167
function removeNode(node: Node): void;
168
169
/**
170
* Move an array element from one index to another
171
* @param list - Reactive array to modify
172
* @param from - Source index
173
* @param to - Target index
174
* @param e - Optional SortableJS event for additional context
175
*/
176
function moveArrayElement<T>(
177
list: MaybeRef<T[]>,
178
from: number,
179
to: number,
180
e?: Sortable.SortableEvent | null
181
): void;
182
```
183
184
### UseSortable Component
185
186
Declarative sortable component for template-based usage.
187
188
```typescript { .api }
189
/**
190
* Declarative sortable component
191
*/
192
const UseSortable = defineComponent({
193
name: 'UseSortable',
194
props: {
195
/** Reactive array model */
196
modelValue: {
197
type: Array as PropType<any[]>,
198
required: true
199
},
200
/** HTML tag to render */
201
tag: {
202
type: String,
203
default: 'div'
204
},
205
/** SortableJS options */
206
options: {
207
type: Object as PropType<UseSortableOptions>,
208
required: true
209
}
210
},
211
emits: ['update:modelValue'],
212
slots: {
213
default: (props: {
214
item: any;
215
index: number;
216
}) => any;
217
}
218
});
219
```
220
221
**Component Usage:**
222
223
```vue
224
<template>
225
<UseSortable
226
v-model="items"
227
tag="ul"
228
:options="sortableOptions"
229
class="sortable-list"
230
>
231
<template #default="{ item, index }">
232
<li :key="item.id" class="sortable-item">
233
<span class="drag-handle">⋮⋮</span>
234
{{ item.text }}
235
<button @click="removeItem(index)">Remove</button>
236
</li>
237
</template>
238
</UseSortable>
239
</template>
240
241
<script setup>
242
import { UseSortable } from "@vueuse/integrations/useSortable";
243
import { ref } from 'vue';
244
245
const items = ref([
246
{ id: 1, text: 'First item' },
247
{ id: 2, text: 'Second item' },
248
{ id: 3, text: 'Third item' }
249
]);
250
251
const sortableOptions = {
252
handle: '.drag-handle',
253
animation: 150,
254
ghostClass: 'ghost-item'
255
};
256
257
const removeItem = (index) => {
258
items.value.splice(index, 1);
259
};
260
</script>
261
262
<style>
263
.sortable-list {
264
list-style: none;
265
padding: 0;
266
}
267
268
.sortable-item {
269
padding: 10px;
270
border: 1px solid #ddd;
271
margin-bottom: 5px;
272
cursor: move;
273
}
274
275
.ghost-item {
276
opacity: 0.5;
277
}
278
279
.drag-handle {
280
color: #999;
281
margin-right: 10px;
282
cursor: grab;
283
}
284
</style>
285
```
286
287
### useFocusTrap
288
289
Focus management and accessibility enhancement using focus-trap.
290
291
```typescript { .api }
292
/**
293
* Focus management and accessibility enhancement
294
* @param target - Target element(s) to trap focus within
295
* @param options - Focus trap configuration options
296
* @returns Focus trap control interface
297
*/
298
function useFocusTrap(
299
target: MaybeRefOrGetter<Arrayable<MaybeRefOrGetter<string> | MaybeComputedElementRef>>,
300
options?: UseFocusTrapOptions
301
): UseFocusTrapReturn;
302
303
interface UseFocusTrapReturn {
304
/** Whether focus trap currently has focus */
305
hasFocus: ShallowRef<boolean>;
306
/** Whether focus trap is paused */
307
isPaused: ShallowRef<boolean>;
308
/** Activate the focus trap */
309
activate: (opts?: ActivateOptions) => void;
310
/** Deactivate the focus trap */
311
deactivate: (opts?: DeactivateOptions) => void;
312
/** Pause the focus trap */
313
pause: Fn;
314
/** Unpause the focus trap */
315
unpause: Fn;
316
}
317
318
interface UseFocusTrapOptions extends Options {
319
/** Activate focus trap immediately */
320
immediate?: boolean;
321
}
322
323
// focus-trap options
324
interface Options {
325
onActivate?: (focusTrapInstance: FocusTrap) => void;
326
onDeactivate?: (focusTrapInstance: FocusTrap) => void;
327
onPause?: (focusTrapInstance: FocusTrap) => void;
328
onUnpause?: (focusTrapInstance: FocusTrap) => void;
329
onPostActivate?: (focusTrapInstance: FocusTrap) => void;
330
onPostDeactivate?: (focusTrapInstance: FocusTrap) => void;
331
checkCanFocusTrap?: (focusTrapContainers: HTMLElement[]) => Promise<void>;
332
checkCanReturnFocus?: (triggerElement: HTMLElement) => Promise<void>;
333
initialFocus?: string | HTMLElement | (() => HTMLElement | string) | false;
334
fallbackFocus?: string | HTMLElement | (() => HTMLElement | string);
335
escapeDeactivates?: boolean | ((e: KeyboardEvent) => boolean);
336
clickOutsideDeactivates?: boolean | ((e: MouseEvent | TouchEvent) => boolean);
337
returnFocusOnDeactivate?: boolean;
338
setReturnFocus?: HTMLElement | string | ((nodeFocusedBeforeActivation: HTMLElement) => HTMLElement | string);
339
allowOutsideClick?: boolean | ((e: MouseEvent | TouchEvent) => boolean);
340
preventScroll?: boolean;
341
tabbableOptions?: TabbableOptions;
342
}
343
344
interface ActivateOptions {
345
onActivate?: (focusTrapInstance: FocusTrap) => void;
346
}
347
348
interface DeactivateOptions {
349
onDeactivate?: (focusTrapInstance: FocusTrap) => void;
350
checkCanReturnFocus?: (trigger: HTMLElement) => Promise<void>;
351
}
352
```
353
354
**Usage Examples:**
355
356
```typescript
357
import { useFocusTrap } from "@vueuse/integrations/useFocusTrap";
358
import { ref } from 'vue';
359
360
// Basic focus trap
361
const modalRef = ref<HTMLElement>();
362
const { activate, deactivate, hasFocus } = useFocusTrap(modalRef);
363
364
// Show modal with focus trap
365
const showModal = () => {
366
// Show modal UI
367
activate();
368
};
369
370
const closeModal = () => {
371
deactivate();
372
// Hide modal UI
373
};
374
375
// Multiple containers
376
const containers = [
377
ref<HTMLElement>(),
378
ref<HTMLElement>()
379
];
380
381
const { activate, deactivate } = useFocusTrap(containers, {
382
immediate: false,
383
escapeDeactivates: true,
384
clickOutsideDeactivates: true
385
});
386
387
// Advanced configuration
388
const { activate, deactivate, pause, unpause } = useFocusTrap(modalRef, {
389
initialFocus: '#first-input',
390
fallbackFocus: '#cancel-button',
391
onActivate: () => console.log('Focus trap activated'),
392
onDeactivate: () => console.log('Focus trap deactivated'),
393
escapeDeactivates: (e) => {
394
// Custom logic for escape key
395
return !e.shiftKey;
396
},
397
clickOutsideDeactivates: false
398
});
399
400
// Temporarily pause focus trap
401
const handleOverlay = () => {
402
pause();
403
// Handle overlay interaction
404
setTimeout(unpause, 1000);
405
};
406
```
407
408
### UseFocusTrap Component
409
410
Declarative focus trap component wrapper.
411
412
```typescript { .api }
413
/**
414
* Declarative focus trap component
415
*/
416
const UseFocusTrap = defineComponent({
417
name: 'UseFocusTrap',
418
props: {
419
/** HTML tag to render */
420
as: {
421
type: [String, Object] as PropType<string | Component>,
422
default: 'div'
423
},
424
/** Focus trap options */
425
options: {
426
type: Object as PropType<UseFocusTrapOptions>,
427
default: () => ({})
428
}
429
},
430
slots: {
431
default: (props: {
432
hasFocus: boolean;
433
isPaused: boolean;
434
activate: (opts?: ActivateOptions) => void;
435
deactivate: (opts?: DeactivateOptions) => void;
436
pause: Fn;
437
unpause: Fn;
438
}) => any;
439
}
440
});
441
```
442
443
**Component Usage:**
444
445
```vue
446
<template>
447
<UseFocusTrap
448
as="div"
449
:options="focusTrapOptions"
450
v-slot="{ hasFocus, activate, deactivate }"
451
>
452
<div v-if="isModalOpen" class="modal">
453
<h2>Modal Title</h2>
454
<input ref="firstInput" placeholder="First input" />
455
<input placeholder="Second input" />
456
<button @click="closeModal">Close</button>
457
</div>
458
</UseFocusTrap>
459
</template>
460
461
<script setup>
462
import { UseFocusTrap } from "@vueuse/integrations/useFocusTrap";
463
import { ref } from 'vue';
464
465
const isModalOpen = ref(false);
466
const firstInput = ref<HTMLInputElement>();
467
468
const focusTrapOptions = {
469
immediate: true,
470
initialFocus: () => firstInput.value,
471
escapeDeactivates: true
472
};
473
474
const closeModal = () => {
475
isModalOpen.value = false;
476
};
477
</script>
478
```