Dashboard

How to Add a Kanban Board to an AI-Built App

A real data model for kanban boards in AI-built apps: boards, columns, cards, fractional-indexed ordering, and what to do when a column with cards in it gets deleted.

Steve Jefferson
Steve Jefferson
Developer Advocate
14 September 20261 min read

How to Add a Kanban Board to an AI-Built App

How to add a kanban board to an AI-built app comes down to three tables and one ordering trick. You need a boards table, a columns table, and a cards table, plus a position value on each card that survives drag-and-drop without renumbering every row on every move. Everything else, the drag handles, the column headers, the card counts, is UI sitting on top of that structure.

Boards are easy to prompt for and easy to get wrong underneath. The visual result looks right in the first five minutes, then falls apart the first time two people move cards at the same time or a column gets deleted with cards still in it. This is the data model and the failure modes to plan for before you ask an AI tool to build the screen.

This assumes you already have the basics of an app in place. If you are earlier than that, start with how to build an app with AI and come back to boards once you have a working data layer to attach one to.

The data model

Three tables, related in a strict hierarchy. A board has many columns, a column has many cards. Keep the relationship one-directional and avoid storing a card's column name as a free-text string, which is how boards quietly end up with four different spellings of "In Progress".

Table

Key columns

Notes

boards

id, name, workspace_id

One row per board. Scope everything below it to workspace_id for access control.

columns

id, board_id, name, position

position orders columns left to right, same fractional scheme as cards.

cards

id, column_id, title, position, assignee_id

column_id changes when a card moves between columns. position orders it within that column.

The assignee, due date, and description fields are ordinary columns on cards, nothing special. Resist the urge to make cards polymorphic so they can represent a task, a bug, and a deal in one table. That flexibility is rarely used and it forces every query to filter on a type column forever.

Ordering without renumbering

The part that breaks in a naive build is the position field. If you store cards as integers 1, 2, 3 and someone drags a card between positions 1 and 2, you have to renumber the rest of the column. That works fine solo and starts corrupting order under concurrent moves, because two renumbering writes can race and leave two cards with the same position.

Fractional indexing avoids this. Store position as a float or a sortable string, and when a card moves between two neighbors, give it the midpoint value instead of renumbering anything else.

New position = halfway between the position of the card above and the card below. Moving to the top of a column: half the top card's position. Moving to the bottom: the bottom card's position plus a fixed step.

Floats do eventually run out of precision after thousands of moves in the same tight spot, so the pragmatic version most AI-built apps ship with uses a sortable string scheme (base-62 fractional keys) that never runs out in practice. If your app builder generates plain float positions, that is fine to start with, just know the fix later is a rebalance pass, not a rewrite of the whole model.

Moving a card: what actually has to happen

  1. Read the target column's cards, ordered by position, to find the new neighbors.

  2. Compute the new position as the midpoint of those neighbors.

  3. Update the card's column_id (if it changed) and position in a single write.

  4. Broadcast the change to anyone else viewing the board, so their view reorders without a full page reload.

Step four is the one most tutorials skip and most real boards need immediately, because a kanban board with two people looking at it and no live sync just looks broken: you move a card, your teammate doesn't see it move, and now you're both dragging things into a state neither of you can see correctly. If your platform has a realtime or subscriptions feature, subscribe the board view to changes on its cards and columns. The same underlying problem, two people changing shared state at once, is worth reading up on directly before you ship a multi-user board: see how to handle two people editing the same record for the concurrency patterns that apply here almost unchanged.

Deleting a column without losing cards

Decide this before a user hits delete on a column that still has twelve cards in it. Three reasonable options, pick one and build the confirmation dialog to match:

  • Block the delete until the column is empty, and say so plainly in the UI.

  • Move the cards to a default column (commonly the first one) automatically.

  • Archive the column and its cards together, recoverable for a period, deleted for good after that.

Archiving is the friendliest option and the easiest to explain to a client, but it means your cards table needs an archived_at field and every card query needs to filter it out. Decide once, prompt for it explicitly, and check that the AI didn't quietly implement a fourth behavior, a hard delete with no confirmation, which is the default a lot of generated CRUD scaffolding falls back to.

Drag-and-drop on the frontend

The interaction itself, picking up a card and dropping it in a new spot, is a solved problem and not worth reinventing. Ask for it using an existing library (most component ecosystems have one) rather than hand-rolled mouse event handling, which is where a surprising amount of generated drag-and-drop code goes wrong on touch devices specifically. If you already built reordering elsewhere in the app, for example a simple list, the same underlying approach extends directly to a board with columns: see how to add drag-and-drop reordering to an AI-built app for the single-list version this builds on.

Get the data model right before the drag handles

If you take one thing from this: sketch the boards, columns, and cards tables and the position scheme before you prompt for any UI. A board that looks finished in a screenshot but reorders cards by renumbering the whole column on every drag will feel fine in a demo and fall over within a week of real use, usually silently, as cards start landing in slightly wrong positions under load. Planning the shape of the data before generating the screen is the general version of this advice: how to plan your data model before building an app with AI covers the same discipline applied to any feature, not just boards.

Once the board itself works, the natural next addition is due dates and a timeline view next to it, at which point estimating how long the underlying work will actually take becomes its own problem, one how to prompt AI to estimate a project timeline is written to help with directly.

Testing it before you call it done

Open the board in two browser tabs signed in as two different users before you ship it. Drag a card in one tab and confirm it moves in the other without a refresh. Then move two different cards into the same column at close to the same time from each tab and check that both land in sane, distinct positions rather than colliding on the same fractional value. This five-minute test catches the two failure modes that a solo click-through never will, and it is the same test worth running on any feature where more than one person can touch the same data at once.

Frequently asked questions

Should columns be a fixed set or fully custom per board?

Custom per board almost always wins for a general tool. A fixed set (To Do, In Progress, Done) is simpler to build but users renaming and reordering columns is one of the first requests you get once real people use the board, so building columns as data from the start avoids a migration later.

Do I need a separate table for swimlanes?

Only if you actually need two independent groupings, for example columns for status and swimlanes for assignee or priority. Most boards don't need this at launch. Add it when a real workflow asks for it, not before, since it roughly doubles the complexity of every position calculation.

How do I show a card count per column without it lagging behind reality?

Compute it from the same live subscription that updates the card list, not a separate cached count field. A stored count that drifts out of sync with the actual rows is a common source of small, confusing bugs that are disproportionately annoying to track down.

What is the simplest way to add labels or tags to cards?

A join table between cards and a small labels table, not a comma-separated text column on the card. It costs one extra table and pays for itself the first time someone wants to filter the board by label.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.