Lait.Umbraco.Content.DragAndDrop
1.2.0
dotnet add package Lait.Umbraco.Content.DragAndDrop --version 1.2.0
NuGet\Install-Package Lait.Umbraco.Content.DragAndDrop -Version 1.2.0
<PackageReference Include="Lait.Umbraco.Content.DragAndDrop" Version="1.2.0" />
<PackageVersion Include="Lait.Umbraco.Content.DragAndDrop" Version="1.2.0" />
<PackageReference Include="Lait.Umbraco.Content.DragAndDrop" />
paket add Lait.Umbraco.Content.DragAndDrop --version 1.2.0
#r "nuget: Lait.Umbraco.Content.DragAndDrop, 1.2.0"
#:package Lait.Umbraco.Content.DragAndDrop@1.2.0
#addin nuget:?package=Lait.Umbraco.Content.DragAndDrop&version=1.2.0
#tool nuget:?package=Lait.Umbraco.Content.DragAndDrop&version=1.2.0
Lait Umbraco Content Drag and Drop
Native HTML5 drag-and-drop and keyboard reordering for the Umbraco 17/18 (Bellissima) backoffice Content tree — plus the bit the tree never let you do: drag an item out of a collection (list view) and drop it anywhere in the content tree.
- Three-zone drop targets per row: above sibling / into as child / below sibling
- Optimistic same-parent reorder — instant visual feedback, rolled back on API error
- Full cross-parent move (reparent) support
- Drag from a collection / list view → drop on the tree — move children out of a list-view page that the tree itself never renders
- Move-in-progress spinner on the node being moved
- "Moved X into/above/below Y" toast in the bottom-right after each successful move (Umbraco's own notification stack)
- Keyboard-accessible reordering (ARIA "grab & place") — WCAG 2.2 AA, with screen-reader announcements
- Hover-to-expand collapsed branches mid-drag
- Cycle guard against dropping a node into its own descendants
Pure App_Plugins package — no C# code, no Startup configuration. Drops in via NuGet and lights
up the moment the assembly is referenced.
Why the collection feature matters
When a document type uses a Collection (list view), its children are shown in a table/grid inside the workspace and are not rendered in the content tree. A plain tree-reorder package can't help there — you can't drag what isn't in the tree. This package recognises a collection row/card as a drag source, so you can grab a child from the list view and drop it onto any node in the tree to reparent it. After the move, the list view refreshes automatically and the item is gone from it.
Project layout
Lait.Umbraco.Content.DragAndDrop/
├─ Lait.Umbraco.Content.DragAndDrop.sln
├─ umbraco-marketplace.json
└─ src/Lait.Umbraco.Content.DragAndDrop/
├─ Lait.Umbraco.Content.DragAndDrop.csproj # Microsoft.NET.Sdk.Razor, net10.0
├─ icon.png
├─ Client/ # TypeScript + Vite source (builds into wwwroot)
│ ├─ package.json tsconfig.json vite.config.ts
│ ├─ public/umbraco-package.json # manifest (copied into wwwroot on build)
│ ├─ src/ # the drag-and-drop modules
│ │ ├─ index.ts # entry point: mount host + install shadow observer
│ │ ├─ observer.ts # patches attachShadow, finds tree items + collection rows
│ │ ├─ dnd-host.ts # controller: drag state, move/sort/reload via DocumentService
│ │ ├─ collection.ts # NEW: recognise a collection row/card as a drag source
│ │ ├─ tree-item.ts drop-zone.ts siblings.ts cycle-guard.ts
│ │ └─ indicator.ts spinner.ts keyboard.ts announce.ts dom.ts constants.ts
│ └─ test/ # vitest unit tests (pure logic)
└─ wwwroot/ # built client, served at /App_Plugins/LaitContentDnd
├─ umbraco-package.json
└─ content-drag-drop.js # generated by `npm run build` / `dotnet build`
The project sets StaticWebAssetBasePath = App_Plugins/LaitContentDnd, so the built wwwroot is served
at /App_Plugins/LaitContentDnd/…. That's how Umbraco 17 discovers umbraco-package.json from a
referenced Razor Class Library — it loads over both a project reference and a NuGet install, with no
manual file copy.
The C# project has no managed code: a BeforeTargets="Build" MSBuild target runs npm install +
npm run build in Client/, emitting the bundle into wwwroot/. The single direct dependency on
Umbraco.Cms.Core exists only so the Umbraco Marketplace can detect the supported version range.
How it works (one-paragraph version)
The script patches Element.prototype.attachShadow at module load to observe every shadow root created
in the backoffice (Bellissima nests everything in shadow DOM), plus walks the existing DOM once at
startup. When it sees an <umb-tree-item entitytype="document"> it makes it draggable; when it sees a
collection row (uui-table-row) or card (umb-document-collection-item-card) it makes that draggable
too and reads the item's key from its workspace edit link (…/document/edit/<guid>). A single hidden
Lit element — mounted inside <umb-app> so it can consume UMB_NOTIFICATION_CONTEXT /
UMB_ACTION_EVENT_CONTEXT — handles the drag events at the document level in the capture phase.
Same-parent reorder mutates the DOM optimistically and calls DocumentService.putDocumentSort;
cross-parent and collection-sourced moves call putDocumentByIdMove then dispatch
UmbRequestReloadChildrenOfEntityEvent to refresh both branches (which also refreshes the list view).
Keyboard reordering
Fully keyboard-operable, as an accessible alternative to dragging (WCAG 2.2 AA):
| Key | Action |
|---|---|
| Space | Grab the focused node / drop it at the chosen position |
| ↑ / ↓ | Move the insertion point between rows |
| → | Nest into the highlighted node (as a child) |
| ← | Pop out to the parent level |
| Esc | Cancel the move |
Each step is announced via an aria-live region. (Enter keeps its normal "open node" behaviour when no
node is grabbed.)
Build the client
From src/Lait.Umbraco.Content.DragAndDrop/Client:
npm install
npm run build # tsc typecheck + vite build → ../wwwroot/content-drag-drop.js
npm test # vitest unit tests
You normally don't need to run this by hand — dotnet build runs it for you via the MSBuild target.
Test in a local Umbraco 17 site
You need an Umbraco 17 website to host the package. Two ways to wire it up:
Easiest: run the helper script (from the repo root, PowerShell): .\test\setup-local-test.ps1.
It installs the U17 template, creates a SQLite-backed site under test/TestSite, references this
project, and runs it. Login: admin@example.com / P@ssw0rd1234. Then open the Content section,
give a document type a Collection (list view), and drag one of its children from the list view onto
a different node in the tree.
A. Project reference (recommended while developing)
dotnet sln <YourSite>.sln add src/Lait.Umbraco.Content.DragAndDrop/Lait.Umbraco.Content.DragAndDrop.csproj
dotnet add <YourSite>/<YourSite>.csproj reference src/Lait.Umbraco.Content.DragAndDrop/Lait.Umbraco.Content.DragAndDrop.csproj
Then dotnet run the site and hard-refresh /umbraco (the manifest cache is ~10s in development mode).
No file copying — the client ships as static web assets and loads over the reference.
B. Local NuGet feed (install with dotnet add package)
Run .\test\pack-local.ps1, or by hand:
dotnet pack src/Lait.Umbraco.Content.DragAndDrop -c Release -o %USERPROFILE%\LocalNuget
dotnet nuget add source %USERPROFILE%\LocalNuget -n LocalFeed # once
dotnet add package Lait.Umbraco.Content.DragAndDrop # in any Umbraco 17 site
Iterating? NuGet won't re-extract a version it already cached. Bump
<Version>in the.csprojbefore re-packing, or clear it:dotnet nuget locals global-packages --clear.
Publish to NuGet + Marketplace
The Marketplace auto-lists any NuGet package that has the umbraco-marketplace tag and a direct
Umbraco.Cms.* dependency — publishing to NuGet.org is the submission. umbraco-marketplace.json
at the repo root supplies the listing metadata.
dotnet pack src/Lait.Umbraco.Content.DragAndDrop -c Release
dotnet nuget push src/Lait.Umbraco.Content.DragAndDrop/bin/Release/Lait.Umbraco.Content.DragAndDrop.1.0.0.nupkg --api-key <KEY> --source https://api.nuget.org/v3/index.json
Bump <Version> before every release — NuGet rejects re-pushing an existing version (409).
Compatibility
| Umbraco | Status |
|---|---|
| 17.x | Target |
| 18.x | Expected (same tree + collection DOM and Management API surface) |
Earlier versions use a different backoffice DOM and Management API and are not supported.
Notes & things to verify against your install
- The collection drag-source reads each item's key from its workspace edit link
(
…/document/edit/<guid>), the one signal both the table and card views render. If a future Umbraco changes that route,Client/src/collection.ts(and its unit test) is the only place to adjust. - The cross-shadow tree probing in
tree-item.tsreads Bellissima internals best-effort; it degrades gracefully (falls back to a server reload) rather than throwing. Umbraco.Cms.Coreis pinned to17.0.0. Bump to match your exact patch version if a restore complains.
Credits
This plugin is a fork of esatto.umbraco.backoffice.contenttreedraganddrop, extended with the ability to drag and drop child items from a collection / list view page onto the content tree — the one thing the original tree-only package can't do.
Author
Anders Bootsmann — @bootsmann1995 · ab@lait.dk
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Umbraco.Cms.Core (>= 17.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.