dioxus-dnd 3.0.0

Modular, accessible drag-and-drop for Dioxus: sortable lists, kanban boards, trees, grids, file drops, multi-select, touch support and more
Documentation
# Multi-select

The mail-client pattern: click selects one item, Ctrl/Cmd+click builds a
stack, and dragging any selected item moves the whole stack as one payload.
There is no special multi-drag machinery underneath. The core is generic
over the payload type, and multi-select is what falls out when that type
is `Vec<K>`.

API reference: [api/multiselect.md](../api/multiselect.md).
Live demo: the
[Mailbox](https://kindintelligence.github.io/dioxus-dnd/mailbox) page,
multi-select triage with a modifier-keyed copy.

## The mental model

Everything rides the ordinary components, parameterized with `Vec<K>`:

- `DndProvider::<Vec<K>>` is the drag world. `K` is your item key type, any
  `Clone + PartialEq` value; ids are typical. Zones are plain
  `DropZone::<Vec<K>>`s and receive the whole selection in one
  `DropOutcome<Vec<K>>`.
- `use_selection::<K>()` returns a `Selection<K>`, shared selection state
  and the single source of truth for what is selected. Like a Dioxus
  signal handle it is `Copy`: passing it into rows hands out keys to the
  same state, so every row stays in sync.
- `SelectableDraggable<K>` wraps each item. It wires the click conventions
  into the selection and resolves its drag payload from it: a selected
  item drags the group, an unselected one drags itself.

Keys keep the payload tiny and your model authoritative: the provider's
type is `Vec<u32>`, not `Vec<Email>`. A drop handler maps keys back to
rows it already owns.

## A complete example

```rust,ignore
let mut selection = use_selection::<u32>();
rsx! {
    DndProvider::<Vec<u32>> {
        for mail in inbox() {
            SelectableDraggable::<u32> {
                key: "{mail.id}",
                item: mail.id,
                selection,
                label: mail.subject,
                MailRow { mail }
            }
        }
        DropZone::<Vec<u32>> {
            label: "Archive",
            on_drop: move |o: DropOutcome<Vec<u32>>| {
                inbox.write().retain(|m| !o.payload.contains(&m.id));
                selection.clear();
            },
            "Archive"
        }
        DragOverlay::<Vec<u32>> { SelectionCount::<u32> {} }
    }
}
```

Note what is absent: no selection bookkeeping in your model, no per-row
event handlers, no multi-drag mode. The selection handle and the `Vec`
payload carry all of it.

## Click, toggle, drag

`SelectableDraggable` implements the platform conventions:

- A plain click selects only that item.
- Ctrl+click (Cmd on macOS) toggles it in or out of the stack.
- Dragging a selected item picks up the whole selection, in selection
  order. Dragging an unselected item picks up just that item, a
  one-element `Vec<K>`.
- The browser fires a trailing `click` on the source after a completed
  pointer drag; letting it through would collapse the just-dragged stack
  to that one item. The component swallows exactly that one click.

The conventions come from `Selection::click`; the rest of the `Selection`
API (`select_only`, `toggle`, `clear`, `items`) is public for custom
interactions, a select-all checkbox or shift ranges, which stay in sync
with the rows automatically because the state is shared.

## The ghost counts, it does not clone

Render a count in the `DragOverlay` instead of cloning rows: dragging
forty items costs the same as dragging one. `SelectionCount` is the
ready-made badge; it reads the in-flight payload and renders "3 item(s)"
(the text is a `DndStrings` closure, see
[Localization](localization.md)). For a custom ghost, read the payload
yourself:

```rust,ignore
#[component]
fn StackGhost() -> Element {
    let dnd = use_dnd::<Vec<u32>>();
    let n = dnd.payload().map(|p| p.len()).unwrap_or(0);
    rsx! { "{n} selected" }
}
```

## Styling selection

Each selected item's wrapper carries `data-selected="true"`, absent when
unselected, so presence-based selectors work directly:

```rust,ignore
SelectableDraggable::<u32> {
    item: mail.id,
    selection,
    class: "rounded data-selected:bg-emerald-100",
    MailRow { mail }
}
```

The attribute follows the shared `Selection`, not component-local state,
so a clear-all button anywhere updates every row. During a stack drag,
every selected item also carries `data-dragging` (on the inner `Draggable`
wrapper), because they all resolve to the same `Vec<K>` payload; dim them
all and the stack visibly leaves.

## Copy versus move

The drop is an ordinary `DropOutcome<Vec<K>>`, so everything from
[Drag and drop](drag-and-drop.md) composes: `accepts` can veto a stack,
`edge` reads insertion sides, and `effect` resolves the modifier keys held
at release. The Mailbox demo files a copy when Ctrl or Cmd is held and
moves otherwise:

```rust,ignore
on_drop: move |o: DropOutcome<Vec<u32>>| {
    if o.effect == DropEffect::Copy {
        labeled.write().extend(o.payload);      // originals stay
    } else {
        inbox.write().retain(|m| !o.payload.contains(&m.id));
    }
    selection.clear();
},
```

## Gotchas

- **Clear the selection in your drop handler.** The library does not guess
  whether a completed drop should keep the stack; call `selection.clear()`
  when it should not.
- **Selection order, not document order.** The payload is the selection
  snapshot: keys arrive in the order they were toggled in. Sort against
  your model if order matters at the destination.
- **Selection building is a click interaction.** Keyboard pickup (Space or
  Enter on a focused row) starts a drag; it does not change the selection.
  Keyboard and switch users can drag whatever is already selected, so give
  them a non-pointer way in, such as a per-row checkbox calling
  `selection.toggle`.
- **Your `class` lands on the outer wrapper.** `data-selected` lives
  there, but `data-dragging` sits one div deeper, on the inner
  `Draggable`. Style drag state with a descendant or `:has()` selector.
  See [Styling]styling.md.
- **Need `disabled`, `touch` or `threshold`?** `SelectableDraggable` does
  not forward them. Compose the core `Draggable::<Vec<K>>` yourself and
  resolve the payload from `selection` the same way.

## Related

- [Drag and drop]drag-and-drop.md: the components this pattern
  parameterizes with `Vec<K>`.
- [Drop effects]drop-effects.md: the modifier convention behind
  copy-versus-move triage.
- [Localization]localization.md: `selection_count` and the rest of the
  announcement strings.
- [Styling]styling.md: the full data-attribute contract.