Skip to content

Demo: Kanban Board ​

A Trello-style board with drag & drop. Three live queries (lists, cards, per-list counts) keep the board in sync; every drop is saved as one atomic transaction that renumbers the source and target lists.

Touch devices

Drag & drop uses the browser's native HTML5 API. On touch screens use the move-to dropdown on each card.

How it works ​

1. Live queries drive the whole board ​

ts
const { data: lists }  = useLiveQuery(() => db.from('lists').orderBy('position'), { db });
const { data: cards }  = useLiveQuery(
  () => db.from('cards').orderBy('list_id').orderBy('position'), { db });
const { data: counts } = useLiveQuery(
  () => db.from('cards').groupBy('list_id').count('id', 'n'), { db });

The board is just cards grouped by list_id in a computed. There is no local copy of the data to keep in sync.

2. A drop is one transaction ​

Moving a card means renumbering position in up to two lists. Wrapping it in db.transaction() makes it atomic, so every live query (and every other open tab) sees one consistent change, never a half-moved card:

ts
await db.transaction(async (tx) => {
  const card = await tx.from('cards').where('id', '=', cardId).first();
  const target = (await tx.from('cards').where('list_id', '=', toListId)
                          .orderBy('position').toArray()).filter((c) => c.id !== cardId);
  target.splice(toIndex, 0, { ...card, list_id: toListId });

  for (const [i, c] of target.entries()) {
    await tx.update('cards', c.id, { list_id: toListId, position: i });
  }
  // ...then close the gap in the source list
});

3. Schema ​

ts
await db.createTable('lists', [
  { name: 'id', type: 'INT32', primaryKey: true, autoInc: true },
  { name: 'title', type: 'TEXT', notNull: true },
  { name: 'position', type: 'INT32', notNull: true },
]);
await db.createTable('cards', [
  { name: 'id', type: 'INT32', primaryKey: true, autoInc: true },
  { name: 'list_id', type: 'INT32', notNull: true, indexed: true },
  { name: 'title', type: 'TEXT', notNull: true },
  { name: 'position', type: 'INT32', notNull: true },
]);

Source code ​

FileWhat it does
kanban/KanbanApp.vueThe board, drag & drop and live queries
kanban/db.tsSchema, seed data, queries and moveCard()
DemoFrame.vueOpens the IndexedDB database and renders the app

👉 Browse the full source on GitHub

Next: Calendar · Typo-Tolerant Search with UDFs · Todo list

Released under the MIT License.