# React Tree View: Checkboxen, Lazy Loading, Drag and Drop

> Bau eine React Tree View mit Tri-State-Checkboxen, Lazy Loading, einem Filter, der Elternknoten behält, Drag and Drop und voller Tastaturbedienung, auch mobil.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 19. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 10 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-tree-view
- Englische Fassung: https://wingo-ui.com/blog/react-tree-view

## Kurz gesagt

Eine brauchbare React Tree View braucht das Tastaturmodell des WAI-ARIA-Musters für Bäume, Tri-State-Checkboxen mit Kaskade, Kinder, die erst beim ersten Aufklappen laden, einen Filter, der den Pfad zu jedem Treffer behält, und Drag and Drop mit einer Alternative per Tastatur. Die Tree View von Wingo UI erledigt das alles aus einem items-Array verschachtelter Knoten; der File Manager baut darauf eine komplette Dokumentenansicht.

Du brauchst eine React Tree View für Ordner, Produktkategorien oder einen Screen für Berechtigungen. Eine rekursive Komponente mit Chevron und etwas Einrückung steht in zehn Minuten. Dann kommen die Anforderungen: Eine Checkbox an einem Ordner soll alles darunter abhaken, ein Zweig mit 2.000 Kindern soll erst laden, wenn er aufgeht, ein Filter soll „invoice“ drei Ebenen tief finden, jemand will eine Datei in einen anderen Ordner ziehen, und das Ganze muss mit Pfeiltasten und auf dem Smartphone funktionieren. Dieser Guide zeigt, was ein Baum für die Produktion braucht, wie die gängigen Optionen im Vergleich abschneiden und welche Props in der [Tree View](https://wingo-ui.com/components/tree-view) von Wingo UI zählen, mit funktionierendem Code für jeden Teil.

## Was muss eine React Tree View können?

Sieben Dinge. Eine rekursive Komponente mit Chevron deckt nur das Erste ab:

1. **Auf- und Zuklappen** mit einer Animation, die die Seite nicht springen lässt.
2. **Tastaturnavigation** nach dem [WAI-ARIA-Muster für Tree Views](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/): ein einziger Tab-Stopp, Pfeiltasten zum Bewegen, Rechts zum Öffnen, Links zum Schließen oder Springen zum Elternknoten, Type-ahead.
3. **Auswahl**, einfach oder mehrfach, getrennt vom Abhaken.
4. **Tri-State-Checkboxen**, bei denen ein teilweise abgehakter Ordner einen Strich zeigt.
5. **Lazy Loading der Kinder** beim ersten Aufklappen, mit Ladezustand und einer Möglichkeit, es erneut zu versuchen.
6. **Ein Filter**, der die Eltern jedes Treffers behält, damit du siehst, wo er liegt.
7. **Drag and Drop zum Verschieben**, dazu ein Weg, dieselbe Verschiebung per Tastatur zu machen.

Das Smartphone bringt zwei weitere mit: Zeilen, die groß genug zum Antippen sind, und ein Zeilenmenü, das als Bottom Sheet aufgeht statt als winziges Popover.

## Wie schneiden die React-Tree-View-Libraries im Vergleich ab?

Die Libraries für eine React Tree View Komponente unterscheiden sich bei drei Features: Lazy Loading der Kinder, Drag and Drop und Checkboxen. Bei MUI X gehören Lazy Loading und Umsortieren zum kostenpflichtigen Pro-Paket, react-arborist bringt Drag and Drop und einen Filter mit, aber seine README erwähnt keine Checkboxen, und shadcn/ui hat überhaupt keine Baum-Komponente, nur einen Sidebar-Block. Die Fakten stammen aus der öffentlichen Doku und den READMEs der Projekte, Stand Oktober 2026.

| Option | Was es ist | Checkboxen | Lazy Loading der Kinder | Drag and Drop | Filter |
|---|---|---|---|---|---|
| [react-arborist](https://github.com/brimdata/react-arborist) | MIT-lizenziertes Paket, virtualisiert, braucht `react-dnd` als Peer Dependency | In der README nicht erwähnt | In der README nicht erwähnt | Sortieren per Drag and Drop | Prop `searchTerm` |
| [MUI X Tree View](https://mui.com/x/react-tree-view/) | MIT-lizenziertes Community-Paket plus kommerzielles Pro-Paket | [`checkboxSelection` mit `selectionPropagation`](https://mui.com/x/react-tree-view/rich-tree-view/selection/) | Pro | Umsortieren ist Pro | Auf der Übersichtsseite nicht aufgeführt |
| [shadcn/ui sidebar-11](https://ui.shadcn.com/blocks/sidebar) | Ein Sidebar-Block mit einklappbarem Dateibaum | Nicht im Block | Nicht im Block | Nicht im Block | Nicht im Block |
| [ReUI Tree](https://reui.io/components/tree) | Komponente aus einer shadcn-Registry auf Basis von `@headless-tree/react` | In einem Beispiel für Berechtigungen zu sehen | Ein Lade-Muster in der FAQ, kein Beispiel | Über dnd kit, laut FAQ | Nur Type-ahead |
| [Wingo UI Tree View](https://wingo-ui.com/components/tree-view) | Pro-Komponente als Quellcode, `npx wingo-ui@latest add tree-view` | Tri-State mit `cascade` | `loadChildren` mit Retry | Maus, langes Drücken und Alt + Pfeiltasten | `searchable` mit hervorgehobenen Treffern |

Läuft deine App schon mit MUI, ist die kostenlose RichTreeView mit Checkbox-Auswahl der kürzere Weg. Willst du einen kostenlosen, virtualisierten Baum und stylst ihn gern selbst, passt react-arborist gut. Wer nach „react tree view shadcn“ sucht, will meist Code, der im eigenen Repo liegt: ReUI, [shadcn-tree-view](https://github.com/MrLightful/shadcn-tree-view) (MIT, mit Drag and Drop) und die Tree View von Wingo UI installierst du alle als Quellcode. Der Rest dieses Guides behandelt die Komponente von Wingo UI.

## Was braucht die Tree View zum Rendern?

Ein `items`-Array mit verschachtelten Knoten. Jeder Knoten hat eine `id`, die im ganzen Baum eindeutig ist, ein `label` und optional `children`.

```bash
npx wingo-ui@latest add tree-view
```

```tsx
"use client";

import { TreeView, type TreeNode } from "@/components/ui/tree-view";

const files: TreeNode[] = [
  {
    id: "app",
    label: "app",
    children: [
      { id: "layout", label: "layout.tsx" },
      { id: "page", label: "page.tsx" },
    ],
  },
  { id: "components", label: "components", children: [{ id: "button", label: "button.tsx" }] },
  { id: "readme", label: "README.md" },
];

export function ProjectFiles({ onOpen }: { onOpen: (id: string) => void }) {
  return (
    <TreeView
      items={files}
      folderIcons
      defaultExpandedKeys={["app"]}
      onAction={(node) => onOpen(node.id)}
      labels={{ tree: "Projektdateien" }}
    />
  );
}
```

So sieht ein React File Tree aus, wie ihn React-Entwickler kennen: `folderIcons` gibt Elternknoten ein Ordner-Icon, das mit ihnen aufgeht, und Blättern ein Datei-Icon. `onAction` läuft, wenn du eine Datei mit Enter, per Doppelklick oder auf dem Smartphone per Tap öffnest. Ohne `onAction` folgt ein Knoten mit `href` seinem Link. Ein Knoten kann außerdem `icon`, `badge`, `description`, `disabled` und eigene `actions` für das ⋯-Menü tragen, im selben Format wie die Einträge im [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu).

Deine Daten kommen vermutlich als flache Zeilen mit `parentId` aus der Datenbank. Wandle sie einmal um:

```ts
import type { TreeNode } from "@/components/ui/tree-view";

type Row = { id: string; parentId: string | null; name: string };

export function toTree(rows: Row[]): TreeNode[] {
  const nodes = new Map<string, TreeNode>(rows.map((row) => [row.id, { id: row.id, label: row.name }]));
  const roots: TreeNode[] = [];
  for (const row of rows) {
    const node = nodes.get(row.id)!;
    const parent = row.parentId ? nodes.get(row.parentId) : undefined;
    if (parent) (parent.children ??= []).push(node);
    else roots.push(node);
  }
  return roots;
}
```

## Wie funktionieren Tri-State-Checkboxen in einer React Tree View?

Schalte `checkboxes` ein und halte `checkedKeys` im State. Mit `cascade` (standardmäßig an) hakt ein Klick auf einen Ordner jeden geladenen, aktivierten Knoten darunter mit ab. Ein Ordner gilt als abgehakt, wenn alle seine Kinder es sind, und zeigt einen Strich, wenn nur ein Teil es ist. Deaktivierte Zeilen werden übersprungen, damit Berechtigungen wie „nur Owner“ leer bleiben, wenn jemand die ganze Gruppe abhakt. Die Zeilen melden den Zustand als `aria-checked`, mit `mixed` für den Strich.

Ein React Tree View Checkbox-Setup für einen Rollen-Editor sieht so aus:

```tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { TreeView, flattenTree, type TreeNode } from "@/components/ui/tree-view";

export function RoleEditor({ permissions }: { permissions: TreeNode[] }) {
  const [checked, setChecked] = useState<string[]>([]);
  // mit cascade stehen voll abgehakte ordner auch in den keys; nur die blätter speichern
  const leafIds = new Set(flattenTree(permissions).filter((row) => !row.hasChildren).map((row) => row.id));

  return (
    <form action="/api/roles/sales-agent" method="post">
      <TreeView
        items={permissions}
        checkboxes
        selectionMode="none"
        checkedKeys={checked}
        onCheckedKeysChange={setChecked}
        defaultExpandedKeys={["sales"]}
        labels={{ tree: "Berechtigungen" }}
      />
      <input type="hidden" name="permissions" value={checked.filter((id) => leafIds.has(id)).join(",")} />
      <Button type="submit">Rolle speichern</Button>
    </form>
  );
}
```

Abhaken und Auswählen sind getrennt. Die Box hakt ab, die Zeile wählt aus. Mit `selectionMode="none"` hakt ein Klick auf eine Dateizeile sie auch ab, während ein Klick auf eine Ordnerzeile sie nur öffnet. Genau das willst du auf einer Seite für Berechtigungen. Um den Zustand eines Ordners auf dem Server nach derselben Regel zu berechnen, nimm das exportierte `getCheckedState(node, checkedKeys)`.

Hak in der Demo unten den Ordner Invoices 2026 ab, nimm dann bei einer seiner Dateien den Haken wieder raus und sieh zu, wie der Ordner einen Strich bekommt. Tipp etwas ins Filterfeld, um den Baum einzugrenzen.

> Live-Demo (Tree View): Hak Invoices 2026 ab, um seine drei Dateien mit abzuhaken, nimm bei einer den Haken raus, um den Strich zu sehen, und tipp dann „contract“ in den Filter: Der Pfad zu jedem Treffer geht auf. Probier sie unter [Tree View](https://wingo-ui.com/components/tree-view) aus und installier sie mit `npx wingo-ui@latest add tree-view`.

## Wie lade ich Kinder per Lazy Loading?

Markier einen Knoten mit `hasChildren` und lass `children` weg. Beim ersten Aufklappen ruft der Baum `loadChildren(node)` auf und zeigt eine Zeile mit Spinner. Schlägt das Promise fehl, tritt eine Retry-Zeile an ihre Stelle. Die Ergebnisse speichert der Baum intern zwischen.

```tsx
"use client";

import { TreeView, type TreeNode } from "@/components/ui/tree-view";

const roots: TreeNode[] = [
  { id: "drive", label: "Geteiltes Laufwerk", hasChildren: true },
  { id: "archive", label: "Archiv", hasChildren: true },
];

async function loadChildren(node: TreeNode): Promise<TreeNode[]> {
  const response = await fetch(`/api/folders/${node.id}/children`);
  if (!response.ok) throw new Error("Konnte nicht geladen werden");
  return (await response.json()) as TreeNode[];
}

export function SharedDrive() {
  return <TreeView items={roots} folderIcons loadChildren={loadChildren} labels={{ tree: "Geteiltes Laufwerk" }} />;
}
```

Unterordner, die du noch nicht geholt hast, können mit `hasChildren: true` zurückkommen. Eine Grenze solltest du einplanen: Der Filter sieht nur geladene Knoten. Ist der Großteil des Baums noch nicht geladen, such auf dem Server und zeig stattdessen die Ergebnisse.

## Wie funktioniert der Filter?

`searchable` setzt ein Filterfeld über den Baum. Treffer behalten ihre Eltern, der Pfad zu jedem Treffer geht auf, und die passenden Buchstaben werden fett. Groß- und Kleinschreibung und diakritische Zeichen spielen nie eine Rolle, „skoda“ findet also „Škoda“. Die Eingabe wird um 150ms entprellt (Debounce). ArrowDown springt vom Feld in den Baum, und Escape leert es. Auf dem Smartphone nutzt das Feld 16px Schrift, damit iOS Safari beim Fokussieren nicht hineinzoomt.

Du kannst den Text mit `filter` und `onFilterChange` steuern, etwa um ihn in der URL zu halten. Ein Knoten, dessen `label` JSX ist, braucht einen `textValue`, sonst haben Filter und Type-ahead nichts, womit sie vergleichen können.

## Wie baue ich Drag and Drop in eine React Tree View ein?

Schalte `draggable` ein und verarbeite `onMove`. Eine Linie zeigt, wo die Zeile landet: vor oder nach einer Zeile oder als Ring um einen Ordner, in den sie hineingeht. Bleibst du über einem geschlossenen Ordner stehen, geht er nach einer kurzen Pause auf, und einen Ordner in sich selbst abzulegen wird verweigert. Auf Touch-Geräten hebt ein langes Drücken von 250ms die Zeile an, damit ein Wischen weiterhin die Seite scrollt; der [Guide zu dnd kit auf dem Smartphone](https://wingo-ui.com/de/blog/dnd-kit-mobile-touch-drag) erklärt, warum diese Verzögerung zählt.

Damit ist React Tree View Drag and Drop per Maus und Touch abgedeckt. Per Tastatur verschieben Alt + ArrowUp und Alt + ArrowDown die fokussierte Zeile unter ihren Geschwistern, Alt + ArrowRight schiebt sie in den Ordner darüber, und Alt + ArrowLeft holt sie eine Ebene heraus. Jede Verschiebung wird für Screenreader angesagt.

`onMove` bekommt `{ id, targetId, position, fromParentId, toParentId, index }`, wobei `index` die Position unter den neuen Geschwistern ist. `moveTreeNode` wendet das auf deine Daten an und lässt unberührte Zweige, wie sie waren:

```tsx
"use client";

import { useState } from "react";
import { TreeView, moveTreeNode, type TreeMoveEvent, type TreeNode } from "@/components/ui/tree-view";

export function Documents({ initial }: { initial: TreeNode[] }) {
  const [items, setItems] = useState(initial);

  const onMove = (event: TreeMoveEvent) => {
    const previous = items;
    setItems(moveTreeNode(items, event));
    fetch(`/api/nodes/${event.id}/move`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ parentId: event.toParentId, index: event.index }),
    })
      .then((response) => {
        if (!response.ok) setItems(previous);
      })
      .catch(() => setItems(previous));
  };

  return <TreeView items={items} draggable folderIcons onMove={onMove} labels={{ tree: "Dokumente" }} />;
}
```

Pro Knoten hält `draggable: false` eine Zeile an ihrem Platz, und `droppable: false` verhindert, dass etwas darin landet. Mit `droppable: true` nimmt ein Blatt Drops an wie ein Ordner.

> Live-Demo (Tree View): Zieh eine Datei in einen anderen Ordner, oder fokussier eine Zeile und drück Alt + ArrowUp, um sie unter ihren Geschwistern zu verschieben. Probier sie unter [Tree View](https://wingo-ui.com/components/tree-view) aus und installier sie mit `npx wingo-ui@latest add tree-view`.

## Welche Tastenkürzel braucht eine Tree View?

Eine Tree View braucht die Tasten aus dem WAI-ARIA-Muster für Bäume: Pfeiltasten zum Bewegen und Öffnen, Home und End, Enter und Leertaste sowie Type-ahead, alles hinter einem einzigen Tab-Stopp. Der Baum hat `role="tree"`, die Zeilen sind `treeitem`s, und offene Ordner enthalten eine `group`. Im Baum gilt:

- **ArrowDown / ArrowUp** wechseln zwischen sichtbaren Zeilen; **Home / End** springen zur ersten und letzten; **PageUp / PageDown** springen um zehn Zeilen.
- **ArrowRight** öffnet einen geschlossenen Ordner oder geht in einen offenen hinein; **ArrowLeft** schließt einen offenen Ordner oder springt zum Elternknoten.
- **Enter** öffnet eine Datei oder klappt einen Ordner auf und zu; **Leertaste** wählt aus oder hakt ab, wenn Checkboxen an sind.
- **Shift + Pfeiltasten** erweitern eine Mehrfachauswahl, **Ctrl + A** (Cmd + A auf dem Mac) wählt jede sichtbare Zeile aus, **Escape** hebt die Auswahl auf.
- **\*** öffnet jeden Ordner neben dem fokussierten, und tippst du Buchstaben, springt der Fokus zur nächsten Zeile, die damit beginnt.
- **Shift + F10** oder die Kontextmenü-Taste öffnet das ⋯-Menü der Zeile.

Das folgt dem WAI-ARIA-Muster, das Type-ahead für jeden Baum empfiehlt, besonders für Bäume mit mehr als sieben Wurzelknoten. Alle Texte, auch die Ansagen beim Verschieben, stecken in `labels` und werden über `DEFAULT_TREE_VIEW_LABELS` gemergt. So übersetzt du den Baum, ohne ihn zu forken.

## Wie verhält sie sich auf dem Smartphone und bei großen Bäumen?

Auf einem Touchscreen wachsen die Zeilen auf 44px, die ganze Zeile ist das Tap-Ziel, ein Tap auf eine Datei löst `onAction` aus, und der ⋯-Button bleibt sichtbar und öffnet ein Bottom Sheet.

Bei großen Bäumen rendert `virtualize="auto"` nur die Zeilen im sichtbaren Bereich, sobald mehr als 300 Zeilen sichtbar sind. Ein virtualisierter Baum ist im DOM flach, setzt `aria-level`, `aria-setsize` und `aria-posinset` an jeder Zeile, lässt die Animation beim Aufklappen weg und nutzt standardmäßig eine Scrollbox mit 420px Höhe. Mit `maxHeight` legst du deine eigene fest.

## Was sind die typischen Fallstricke bei einer Tree View?

- **Doppelte IDs.** Auswahl, Aufklappen, Haken und Verschieben laufen alle über `id`, sie muss also im ganzen Baum eindeutig sein, nicht nur unter Geschwistern. Im Entwicklungsmodus nennt eine Warnung die doppelten IDs.
- **Ordner-IDs aus der Kaskade speichern.** Voll abgehakte Ordner stehen mit in `checkedKeys`. Speicher die Blätter wie im Rollen-Editor oben, sonst muss dein Backend wissen, welche IDs Ordner sind und was sie beim Speichern enthielten.
- **Verschieben, ohne `items` zu aktualisieren.** Der Baum wird von deinen Daten gesteuert. Aktualisiert `onMove` die `items` nicht, springt die Zeile zurück.
- **Ein Baum für zwei Ebenen.** Eine Liste von Abschnitten mit je ein paar Einträgen liest sich besser als [Accordion](https://wingo-ui.com/components/accordion) oder als Gruppe in einer [Sidebar](https://wingo-ui.com/components/sidebar).

## Wann nehme ich stattdessen den File Manager?

Wenn der Baum das Seitenpanel einer Dokumentenansicht ist. Der [File Manager](https://wingo-ui.com/components/file-manager) nimmt eine flache `items`-Liste aus `{ id, kind, name, parentId }` und zeigt Ordner und Dateien als Raster oder Liste, mit einem Ordnerbaum (auf Basis der Tree View) daneben, sobald der Manager 768px breit ist. Er ergänzt Uploads mit Fortschritt, Umbenennen direkt in der Zeile, Verschieben über „Move to“, Massenaktionen und eine Suche über alle Ordner. Sein `buildFolderTree` macht aus der flachen Liste Knoten für die Tree View, dieselbe Aufgabe wie der Helfer `toTree` oben.

> Live-Demo (File manager): Doppelklick auf einen Ordner öffnet ihn, dann zieh eine Datei auf einen anderen Ordner oder einen Breadcrumb, um sie zu verschieben; Rechtsklick auf eine Datei oder ihr ⋯-Button bringt Rename und Move to. Probier sie unter [File manager](https://wingo-ui.com/components/file-manager) aus und installier sie mit `npx wingo-ui@latest add file-manager`.

## Wie baue ich sie in mein Projekt ein?

Führ `npx wingo-ui@latest add tree-view` aus (oder `file-manager`). Die CLI kopiert den Quellcode nach `components/ui`, ergänzt die Komponenten und Hooks, die er importiert, und installiert `@dnd-kit/core`, `@tanstack/react-virtual`, `motion` und `lucide-react`. Beide sind Pro-Komponenten ([Preise](https://wingo-ui.com/de/pricing)); die [Checkbox](https://wingo-ui.com/components/checkbox), die sie nutzen, ist kostenlos. Weitere Deep Dives zu einzelnen Komponenten findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components) und im [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide). Ist dein Baum eigentlich ein JSON-Payload, deckt der [Guide zum React JSON Viewer](https://wingo-ui.com/de/blog/react-json-viewer) diesen Fall ab.

## Komponenten in diesem Artikel

- [Tree View](https://wingo-ui.com/components/tree-view) (Pro): A React tree view for folders, categories or permissions: animated expand, selection, tri-state checkboxes, filter and drag to move. Installation: `npx wingo-ui@latest add tree-view`
- [File manager](https://wingo-ui.com/components/file-manager) (Pro): A React file manager for the documents of a company or a record: folders and files in a grid or a list, a folder tree, uploads and bulk actions. Installation: `npx wingo-ui@latest add file-manager`

## Häufige Fragen

### Hat shadcn/ui eine Tree-View-Komponente?

Nein, Stand Oktober 2026. shadcn/ui liefert einen Sidebar-Block mit einklappbarem Dateibaum (sidebar-11) auf Basis von Collapsible, aber keine Baum-Komponente mit Tastaturnavigation, Checkboxen oder Drag and Drop. Community-Registries wie ReUI und shadcn-tree-view von mrlightful sowie die Tree View von Wingo UI schließen diese Lücke.

### Wie füge ich einer React Tree View Checkboxen hinzu?

Schalte checkboxes ein und halte checkedKeys im State. Mit cascade (Standard) hakt ein Klick auf einen Ordner alles darunter mit ab, und ein Ordner, bei dem nur ein Teil der Kinder abgehakt ist, zeigt einen Strich: den Zustand indeterminate.

### Wie lade ich Kindknoten in einem React-Baum per Lazy Loading?

Markier einen Knoten mit hasChildren, lass children weg und übergib eine Funktion loadChildren, die ein Promise mit den Kindknoten zurückgibt. Die Tree View zeigt während des Ladens eine Zeile mit Spinner, eine Retry-Zeile, wenn das Promise fehlschlägt, und speichert das Ergebnis zwischen.

### Wie wandle ich eine flache Liste mit parentId in einen Baum um?

Bau in einem Durchlauf eine Map von ID zu Knoten und häng dann jeden Knoten an die children seines Elternknotens oder an die Wurzelliste. Der Helfer in diesem Artikel schafft das in etwa zehn Zeilen, und buildFolderTree im File Manager macht dasselbe für seine Ordner.

### Ist die Tree View von Wingo UI kostenlos?

Nein. Tree View und File Manager sind Pro-Komponenten. Checkbox und Dropdown Menu, auf denen sie aufbauen, sind kostenlos.

---

Quelle: https://wingo-ui.com/de/blog/react-tree-view
