---
title: "Projekt-Board"
description: "Ein Produkt-Board im Stil von Linear und Trello – Spalten, die du umsortierst, WIP-Limits, eine Frage vor Done, Mehrfachauswahl mit Auswahlleiste, Filter, durch die die Drags hindurch funktionieren, ein Papierkorb mit Undo und ein Karten-Panel, dessen Status-Buttons echte Drags abspielen."
canonical_url: "https://zizigy.github.io/vue-dnd-kit/de/docs/examples/board"
---

> Index of every page on this site: [llms.txt](https://zizigy.github.io/vue-dnd-kit/llms.txt)

<example height="40rem" hint="Ziehe Karten und Spalten; ein Klick öffnet eine Karte, Strg/Cmd-Klick wählt sie aus" name="board" title="Projekt-Board">



</example>

## Worauf du achten solltest

- **Board.vue**: Die Spalten sind eine eigene horizontale sortierbare Liste (`strategy: 'horizontal'`, `axis: 'x'`), und jede Spalte ist ein sortierbares Element, das an seinem Kopf
gezogen wird (`handle`). Aufgeklappt füllt das Board den Bildschirm; auf einem Handy scrollt es
seitwärts.
- **Column.vue**: Die Karten aller Spalten bilden eine `group`, mit den Regeln der Spalte: ihrem
WIP-Limit, der Frage vor Done und den Filtern. Die Anzeige in ihrem Kopf hebt die Karten hervor,
die ein Drag hereinbringen würde.
- **Card.vue**: ein sortierbares Element mit `type: 'card'`, sodass der Papierkorb Karten annimmt
und keine Spalten. Ein Klick öffnet die Karte, Strg/Cmd-Klick wählt sie aus (`selected`); mit der
Tastatur nimmt Space sie auf, Enter öffnet sie und X wählt sie aus.
- **store.ts**: die Daten und Regeln des Boards, ganz ohne Drag and Drop, oberhalb des
`DnDProvider` bereitgestellt, damit auch das Overlay sie liest.

## Die Regeln einer Spalte

Jede Spalte gibt ihrer Liste vier Regeln. `put` sieht alle Karten eines Multi-Drags auf einmal:
Die Spalte nimmt sie alle, wenn sie unter ihr Limit passen, oder keine. Über einer vollen Spalte
bleibt der Slot dort, wo der Drag gestartet ist, die Spalte wird rot, und die Karten in der Hand
sagen, warum. `onBeforeDrop` fragt nach, bevor Karten nach Done kommen, und `animateChanges` lässt die
Karten gleiten, die ohne Drag kommen und gehen: ein Filter, Add card, Undo.

```ts [Column.vue]
const { items, isOver, isAccepting } = makeSortableList(list, {
  id: props.column.id,
  items: () => props.column.cards.filter(board.matches),
  group: { name: 'cards', put: ({ keys }) => board.fits(props.column, keys) },
  overlay: CardOverlay,
  animateChanges: true,
  onBeforeDrop: (event) => (props.column.confirm && !event.sameList ? board.ask(event.items) : 'accept'),
  onDrop: (event) => {
    event.preventDefault();
    board.apply(event);
  },
});
```

## Filter und Drops, die das Board anwendet

Die Suche und die Personen in der Werkzeugleiste filtern die Karten, und jeder Drag arbeitet
weiter auf dem, was gezeigt wird. Jede Liste liest ihre Spalte über einen Getter, gefiltert,
sodass die Bibliothek sie nie schreibt: `onDrop` ruft `preventDefault()` auf und gibt `e.result`,
die neuen gefilterten Karten jeder Spalte, die der Drop geändert hat, an das Board. Das Board
setzt sie zurück auf die Plätze, die die gezeigten Karten hatten, und die Karten, die die Filter
ausblenden, bleiben, wo sie waren.

```ts [store.ts]
const merge = (all: Card[], shown: Card[]) => {
  const queue = [...shown];
  const merged: Card[] = [];
  for (const entry of all) {
    if (!matches(entry)) merged.push(entry);
    else if (queue.length) merged.push(queue.shift()!);
  }
  // Cards that came in from another column and found no place left: at the end.
  return [...merged, ...queue];
};
```

## Die Karten in der Hand

**CardOverlay.vue** ist das `overlay` der Karten. Die gegriffene Karte hebt sich und neigt sich,
der Rest einer Auswahl liegt darunter, gebaut aus `DragPreview`-Klonen, die über das `transform`
ihrer Wrapper gedreht werden, und obendrauf sitzt eine Anzahl. Unter den Karten sagt ein Hinweis,
was das Loslassen tun würde, aus `over` und `accepted` des Overlays: *Move 2 cards to In review*,
*Complete*, *Delete* oder *In review is full · 2 max*. In eine Spalte abgelegt, landet jeder Klon
auf seinem eigenen Slot; über dem Papierkorb schrumpft der Stapel, und dort abgelegt gehen die
Karten eine nach der anderen hinein.

```ts [CardOverlay.vue]
const hint = computed(() => {
  if (props.status !== 'dragging' || !props.over) return null;
  const many = props.items.length > 1 ? ` ${props.items.length} cards` : '';
  if (props.over.id === 'trash') return { tone: 'danger', icon: 'trash', text: `Delete${many}` };
  const column = board.column(props.over.id);
  if (!column) return null;
  if (!props.accepted) return { tone: 'danger', icon: 'limit', text: `${column.title} is full · ${column.limit} max` };
  if (board.find(props.items[0]!.id)?.column.id === column.id) return null;
  if (column.confirm) return { tone: 'done', column, text: `Complete${many}` };
  return { tone: '', column, text: `Move${many} to ${column.title}` };
});
```

Eine Spalte, die an ihrem Kopf gezogen wird, ist der Standard-Klon, mit der Eigenschaft `rotate`
geneigt, solange `data-dnd-overlay` `dragging` lautet.

## Done fragt zuerst

Ein Drop nach Done gibt aus `onBeforeDrop` ein Promise zurück: Die Karten warten dort, wo sie
losgelassen wurden, während **ConfirmDialog.vue** fragt. *Move to Done* antwortet `'accept'`,
Cancel und Escape `'reject'` (die Karten fliegen zurück), und *Keep dragging* `'continue'`: Die
Karten folgen wieder dem Zeiger, und der nächste Klick legt sie ab. Sobald die Karten gelandet
sind (`onDragSettle`), erscheint ihr Haken in Done, und der Fortschritt des Sprints bewegt sich.

```ts [Board.vue]
useDnDMonitor({
  onDragSettle: (event) => {
    const target = event.over ? board.column(event.over.id) : undefined;
    if ('dropped' in event && target?.confirm && event.source?.list !== target.id) board.complete(event.accepted);
  },
});
```

## Mehrere Karten, der Papierkorb und Undo

Ausgewählte Karten reisen zusammen: Ziehe eine, und jede ausgewählte Karte kommt mit, aus jeder
Spalte. Die Auswahlleiste verschiebt sie, löscht sie oder hebt die Auswahl auf (Escape auch).
**Trash.vue** ist ein einfaches Drop-Ziel, das nur Karten annimmt; es löscht sie vom Board, wie
es die Delete-Buttons tun, sodass die Meldung jede wieder dorthin zurücklegen kann, wo sie war.

```ts [Trash.vue]
makeDroppable(el, {
  id: 'trash',
  accept: 'card',
  onDrop: (event) => {
    board.remove(event.active.ids);
    selection.delete(...event.active.ids);
  },
});
```

## Verschieben ohne Drag

Das Panel einer Karte hat Status-Buttons, und die Auswahlleiste hat einen Button pro Spalte. Sie
verschieben Karten mit `@vue-dnd-kit/actions`: `moveItem` spielt einen echten Drag der Karte per
Tastatur ab, und eine ausgewählte Karte bringt die Auswahl mit. Er wird angesagt und animiert wie
deiner, und das Limit der Spalte und die Frage von Done gelten für ihn wie für jeden Drag.

```ts [store.ts]
export function useMove() {
  const board = inject(BOARD)!;
  const { moveItem } = useDnDActions();
  return (ids: Key[], target: Column) => {
    if (!board.fits(target, ids)) return board.notify(`${target.title} is full: ${target.limit} cards at most`);
    void moveItem(ids[0]!, { list: target.id });
  };
}
```

## Tastatur und Screenreader

Nur Space nimmt eine Karte auf, Enter bleibt also frei, um sie zu öffnen; die Anleitung sagt das.
Die Pfeile bewegen eine Karte slotweise und quer in die nächste Spalte, die sie annimmt: Eine
volle wird übersprungen, und hinter der letzten Spalte trägt der nächste Druck die Karte weiter
zum Papierkorb. Die Ansagen nennen Karten und Spalten statt IDs: *„Picked up VDK-45, Haptics on
Android tablets. Todo, position 6 of 6.“*, *„In review is full: its limit is 2 cards.“*

```vue [index.vue]
<DnDProvider
  :keyboard="{ keys: { start: ['Space'] } }"
  screen-reader-instructions="Space picks up a card or a column, the arrow keys move it, Space drops it, Escape cancels. Enter opens a card, X selects it."
  :announcements="announcements"
  :haptics="{ enter: true, refuse: true }"
>
```

Mehr dazu: [Listen mit gemeinsamen Elementen](https://zizigy.github.io/docs/guides/shared-lists#controlled-lists) ·
[Über einen Drop entscheiden](https://zizigy.github.io/docs/guides/drop-decisions) · [Multi-Drag](https://zizigy.github.io/docs/guides/multi-drag) ·
[Das Overlay](https://zizigy.github.io/docs/guides/overlay#dragpreview-the-clone-in-your-layout) ·
[Programmatisches Ziehen](https://zizigy.github.io/docs/guides/sensors#programmatic-drags) ·
[Barrierefreiheit](https://zizigy.github.io/docs/guides/accessibility)

## Sitemap

See the full [sitemap](https://zizigy.github.io/vue-dnd-kit/sitemap.md) for all pages.
