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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Lait.Umbraco.Content.DragAndDrop" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lait.Umbraco.Content.DragAndDrop" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Lait.Umbraco.Content.DragAndDrop" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Lait.Umbraco.Content.DragAndDrop --version 1.2.0
                    
#r "nuget: Lait.Umbraco.Content.DragAndDrop, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Lait.Umbraco.Content.DragAndDrop@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Lait.Umbraco.Content.DragAndDrop&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Lait.Umbraco.Content.DragAndDrop&version=1.2.0
                    
Install as a Cake Tool

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 .csproj before 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.ts reads Bellissima internals best-effort; it degrades gracefully (falls back to a server reload) rather than throwing.
  • Umbraco.Cms.Core is pinned to 17.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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 132 6/25/2026
1.1.0 110 6/23/2026
1.0.2 97 6/23/2026
1.0.1 107 6/23/2026
1.0.0 105 6/23/2026