dnd_kit_flutter 0.6.0
dnd_kit_flutter: ^0.6.0 copied to clipboard
Flutter drag-and-drop toolkit with core widgets, sensors, measuring, overlays, and sortable presets.
dnd_kit_flutter #
dnd_kit_flutter is the Flutter adapter of the dnd_kit toolkit for building
sortable lists, grids, Kanban boards, dashboards, canvas editors, and other
drag-heavy interfaces. It builds on the framework-agnostic dnd_kit engine.
The package is centered on Flutter-native widgets and controllers:
DndScopeandDndControllercoordinate drag state.DndDraggableregisters draggable widgets.DndDroppableregisters drop targets.DndDragOverlayrenders an independent drag visual.SortableScopeandSortableItemprovide stable same-container list and grid sorting presets.
Applications own their data. The library reports drag, drop, and sortable move intent; your app updates its own lists, boards, stores, or documents.
Import #
import 'package:dnd_kit_flutter/dnd_kit_flutter.dart';
This entry point also exports the pure Dart dnd_kit engine primitives such as
DndId, DndRect, collision detectors, modifiers, events, and drag state.
Basic Drag And Drop #
Wrap the drag-and-drop area in a DndScope, then place draggables and
droppables inside it.
DndScope(
child: Stack(
children: [
DndDroppable(
id: const DndId('inbox'),
builder: (context, details, child) {
return DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: details.isOver ? const Color(0xff2563eb) : const Color(0xffd1d5db),
),
),
child: child,
);
},
child: const SizedBox(width: 240, height: 160),
),
DndDraggable(
id: const DndId('task-1'),
onDragEnd: (event) {
final overId = event.overId;
if (overId == const DndId('inbox')) {
// Update application-owned state here.
}
},
child: const Card(child: ListTile(title: Text('Task 1'))),
),
DndDragOverlay(
builder: (context, details) {
return const Card(child: ListTile(title: Text('Task 1')));
},
),
],
),
)
Use DndDraggable.builder and DndDroppable.builder when visuals need to
react to active, dragging, dropping, or over states.
Sortable Lists And Grids #
Use SortableScope to provide the current item order and SortableItem for
each child. The callback tells the app what moved; it does not mutate the list.
SortableScope(
itemIds: items.map((item) => DndId(item.id)),
strategy: SortableStrategies.verticalList,
onMove: (details) {
setState(() {
final item = items.removeAt(details.fromIndex);
items.insert(details.toIndex, item);
});
},
child: ListView(
children: [
for (final item in items)
SortableItem(
id: DndId(item.id),
child: ListTile(title: Text(item.title)),
),
],
),
)
Stable strategies include:
SortableStrategies.verticalListSortableStrategies.horizontalListSortableStrategies.gridSortableStrategies.dropOnOver
The first three resolve the target index from the dragged rect's center, so a
drop commits only once that center crosses a neighbour's center. If your UI
highlights the drop target or opens a placeholder gap, use
SortableStrategies.dropOnOver instead: it lands the move on the item the
drag is currently over, which is exactly what isOver reports.
Live sortable feedback #
During a drag, SortableItemDetails reports where the item would land
(previewIndex, previewContainerId) and how far each item should move to
make room (offset). Set an offsetResolver to enable the offsets:
SortableScope(
itemIds: order,
strategy: SortableStrategies.dropOnOver,
offsetResolver: SortableOffsets.verticalList,
onMove: _onMove,
child: ListView(
children: [
for (final id in order)
SortableItem(
id: id,
builder: (context, details, child) => AnimatedSlide(
duration: const Duration(milliseconds: 150),
offset: Offset(0, details.offset.y / itemHeight),
child: child,
),
child: CardTile(id),
),
],
),
)
The library reports geometry and never animates; you choose the animation. The
default is SortableOffsets.none, so nothing moves unless you ask.
Apply the offset inside the builder rather than around the SortableItem:
the builder's output sits below the measured box, so the shift cannot change a
measured rectangle and feed back into collision detection.
Customization #
Core behavior is intentionally open:
- pass a custom
DndCollisionDetectortoDndController; - compose built-in detectors with
DndCollisionDetectors.compose; - constrain movement with
DndModifiervalues such asDndModifiers.restrictToVerticalAxisorDndModifiers.snapToGrid; - use
DndLongPressActivationorDndSensorActivationConstraintto tune activation; - attach
DndDiagnosticsConfig.onWarningto surface duplicate ID and registry warnings.
Accessibility #
dnd_kit_flutter keeps the Flutter adapter's accessibility model adapter-local
and Flutter-native. DndAnnouncements comes from the shared dnd_kit engine,
while DndDraggable and DndDragHandle accept optional semantics labels and
hints and DndScope can opt into drag lifecycle announcements for assistive
technologies.
DndScope(
announcements: const DndAnnouncements(),
child: DndDraggable(
id: const DndId('task-1'),
label: 'Quarterly planning task',
hint: 'Press Space to pick up, arrow keys to move, Enter to drop.',
child: ListTile(
title: const Text('Quarterly planning'),
trailing: const DndDragHandle(
label: 'Reorder handle',
hint: 'Drag from here to move this task.',
child: Icon(Icons.drag_indicator),
),
),
),
)
Announcements are derived from shared controller state transitions and the
shared DndAnnouncements contract, so keyboard and pointer drags speak the
same start, over-target, drop, and cancel events without introducing a second
drag runtime.
dnd_kit family #
| Package | Use it for |
|---|---|
dnd_kit_flutter |
Flutter apps — widgets, sensors, overlays, and sortable presets. |
dnd_kit_jaspr |
Jaspr (Dart web) apps — the web adapter over the shared engine. |
dnd_kit |
The shared, framework-agnostic engine. |