Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React Kanban Board: Drag, WIP Limits, Phone Layout
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React Kanban Board: Drag, WIP Limits, Phone Layout
Component guides
Component guides

React Kanban Board: Drag, WIP Limits, Phone Layout

SR
Serban Rusu · Founder of Wingo UI
Oct 16, 2026 · 9 min read

On this page

0%
  1. What should a React kanban board component handle?
  2. How do the React kanban libraries compare?
  3. What props does a React kanban board need?
  4. How do kanban WIP limits work in the board?
  5. How do I save moves and add cards from a server?
  6. How do I make a kanban board work on mobile?
  7. How do I make a kanban board accessible?
  8. What are the common React kanban board pitfalls?
  9. When should I use the Deal Pipeline instead?
  10. How do I add it to my project?
  11. FAQ
    1. What is the best React kanban library?
    2. How do WIP limits work on a kanban board?
    3. How do I make a kanban board work on mobile?
    4. Is there a shadcn kanban component?
    5. Is the Wingo UI Kanban free?

TL;DR

A production React kanban board needs mouse drag, a long press on touch, Space and arrow keys, and a Move to menu for people who cannot drag. It also needs WIP limits that warn or refuse drops, and a phone layout that shows one column at a time. The Wingo UI Kanban does this from a columns array and a controlled value; the Deal Pipeline adds stage probabilities and weighted totals on top.

Published Oct 16, 2026

You need a React kanban board for tasks, orders or a sales pipeline. Dragging cards between columns with a mouse is the easy part, and most tutorials stop there. Then someone opens it on a phone and the five columns shrink to slivers, a swipe drags a card when they meant to scroll, a keyboard user cannot move anything, and "In progress" quietly fills up with twelve cards. This guide covers what a production board needs, the props that matter in the Wingo UI Kanban, and a full example with saving, inline add and WIP limits.

What should a React kanban board component handle?

Six things: three ways to drag, a way to move without dragging, WIP limits, inline add, a phone layout and screen reader announcements. Most React kanban board component demos only show the first one, a mouse on a wide screen. Here is the list we hold our own board to:

  1. Three ways to drag. Mouse after 8px of movement (so a click still opens the card), touch after a 250ms long press (so a swipe still scrolls), keyboard with Space and the arrow keys.
  2. A way to move without dragging. WCAG 2.2 success criterion 2.5.7 Dragging Movements (opens in a new tab) (level AA) asks for a single-pointer alternative to any drag unless dragging is essential. A Move to menu covers it.
  3. WIP limits that warn or refuse the drop.
  4. Inline add that stays open for the next card.
  5. A phone layout with one column at a time.
  6. Announcements of every move for screen readers.

Point 5 is the one demos skip: five 288px columns need 1,440px plus gaps, so on a 390px screen you either scroll sideways past slivers of columns or shrink cards until titles wrap after two words.

How do the React kanban libraries compare?

Dice UI is a free source component you install through the shadcn CLI, SVAR is a packaged widget built for large boards, and the Wingo UI Kanban is a Pro source component with a phone layout and WIP limits built in. These are the three names that come up when you search for a React kanban library or a shadcn kanban. Facts are from each project's public docs (Dice UI's MIT license from its GitHub repository), as of October 2026.

OptionWhat it isDrag engineKeyboardPhone layout and WIP limits
Dice UI Kanban (opens in a new tab)Free MIT source component, installs with npx shadcn@latest add @diceui/kanbandnd kitEnter or Space picks up, arrows move, Escape cancelsNot mentioned in the docs
SVAR React Kanban (opens in a new tab)Packaged library, MIT core edition plus a paid PRO edition (export to PDF, PNG and Excel, undo and redo, dynamic column loading)Built in, no extra drag libraryNot mentioned on the product pageNot mentioned on the product page
Wingo UI KanbanPro source component, installs with npx wingo-ui@latest add kanbandnd kitSpace picks up, arrows move across columns, Space or Enter drops, Escape cancelsOne column per screen below 576px, limit per column with warn or block

Dice UI is a good choice if you want a free set of composable parts and plan to build the phone behavior yourself. SVAR fits if you want a configured widget: its page lists card and column virtualization, which matters once a board holds thousands of cards. The rest of this guide covers the Wingo UI board.

What props does a React kanban board need?

Two arrays. columns lists the columns in order, and value (or defaultValue) lists the cards, each with a columnId. The board calls onValueChange with the whole new array after a drop, a Move to or an added card.

bash
npx wingo-ui@latest add kanban
tsx
"use client";
import { useState } from "react";
import { Kanban, type KanbanColumnData, type KanbanItem } from "@/components/ui/kanban";
const columns: KanbanColumnData[] = [
{ id: "lead", title: "New lead", tone: "info" },
{ id: "offer", title: "Proposal sent", limit: 5 },
{ id: "won", title: "Won", tone: "success" },
];
export function Pipeline() {
const [deals, setDeals] = useState<KanbanItem[]>([
{ id: "c02", columnId: "offer", title: "Northwind Logistics", amount: 48920, dueDate: "2026-09-26" },
{ id: "c05", columnId: "won", title: "Summit Builders", amount: 103400, assignees: [{ name: "Daniel Reed" }] },
]);
return (
<div className="h-[560px]">
<Kanban columns={columns} value={deals} onValueChange={(next) => setDeals(next)} locale="en-US" />
</div>
);
}

The board fills its container's height, so give the wrapper one.

A card takes title, description, tags, assignees, dueDate (red once it is overdue), priority, amount, a 16:9 cover image and a muted meta line. Amounts are summed into the column header in currency (USD by default); set a column's total to override the sum or null to hide it. The props you will change most often after that are limitBehavior, columnWidth (288px by default), size (sm for dense overview boards) and readOnly.

Drag a card to another column, then tab to a card and press Space to move it with the arrow keys. Open a card's ⋯ menu to find Move to.

Drag a deal into Negotiation and watch the column totals change. Tab to a card, press Space, move it with the arrow keys and press Space again to drop it, or use ⋯ then Move to
$ npx wingo-ui@latest add kanban
ProKanban docs

How do kanban WIP limits work in the board?

A WIP (work in progress) limit is the most cards a column should hold at the same time. Kanban WIP limits exist to make a bottleneck visible: when "In review" is full, the team finishes reviews before starting new work. Jira's column constraints (opens in a new tab) (documented for Jira Software Server) color a column red when it passes its maximum and yellow when it is under its minimum, as a visual signal only.

The Kanban supports both styles. Give a column a limit and its count reads "3 / 5":

  • limitBehavior="warn" (the default): at the limit the count turns amber, over it red with a one-time pulse and a line of text ("Over the limit: 6 of 5"). The drop still goes through.
  • limitBehavior="block": a full column refuses drops, dims while you drag, and its entry in the Move to menu is disabled.
tsx
{ id: "doing", title: "In progress", tone: "info", limit: 3, description: "At most 3 at a time" }

Use warn for teams that treat the limit as a conversation, and block for boards where the limit is a real capacity, such as a packing station that can only handle a fixed number of orders. A column with disabled: true accepts no drops at all, while its cards can still leave.

Proposal sent has a limit of 5 and holds 4 deals. Drag one more in, then try a second: the full column dims and refuses it
$ npx wingo-ui@latest add kanban
ProKanban docs

How do I save moves and add cards from a server?

Update state at once, send the change, and roll back if the request fails. onValueChange gets the new array and, for moves, a second argument: { itemId, fromColumnId, toColumnId, fromIndex, toIndex }. toIndex is the position inside the target column, not in the flat value array, so send it with the column id and let the server renumber that column's sort keys. onMove runs before a move commits; return false to cancel it and the card flies back.

onAddCard shows + Add card under every column. Return the new card, or a promise of it, and the board inserts it at the end of the column. While the promise is pending the Add button shows a loading state, and if it rejects the text stays in the composer so nothing is lost. Enter adds and keeps the composer open for the next card, Shift + Enter makes a new line, and Escape closes it.

tsx
"use client";
import { useState } from "react";
import { Kanban, type KanbanColumnData, type KanbanItem } from "@/components/ui/kanban";
export function TaskBoard({ columns, initial }: { columns: KanbanColumnData[]; initial: KanbanItem[] }) {
const [tasks, setTasks] = useState(initial);
return (
<div className="h-[calc(100dvh-8rem)]">
<Kanban
columns={columns}
value={tasks}
limitBehavior="block"
// done cards cannot go back to the backlog
onMove={(event) => !(event.fromColumnId === "done" && event.toColumnId === "todo")}
onValueChange={(next, move) => {
const previous = tasks;
setTasks(next);
if (!move) return;
fetch(`/api/tasks/${move.itemId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ columnId: move.toColumnId, index: move.toIndex }),
})
.then((response) => {
if (!response.ok) setTasks(previous);
})
.catch(() => setTasks(previous));
}}
onAddCard={async (columnId, title) => {
const response = await fetch("/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ columnId, title }),
});
if (!response.ok) throw new Error("Not saved");
return (await response.json()) as KanbanItem;
}}
locale="en-US"
/>
</div>
);
}

If you do not update value after onValueChange, the card springs back. That is the controlled contract, and it is why the rollback above is just a setTasks(previous). The snapshot is only safe for one request at a time: if a second move lands before the first one fails, restoring the first snapshot also undoes the second. When moves can overlap, refetch the board on a failure instead.

How do I make a kanban board work on mobile?

Show one column at a time. When the board is narrower than 576px, each column takes 85% of the width with the next one peeking from the edge, and the board snaps from column to column. A chip row on top shows every column with its count, lights the one in view, and jumps to a column on tap; mobileNav={false} hides it. The switch uses a container query on the board, so a narrow board in a side panel on a desktop gets the phone layout too.

Touch drag starts with a 250ms long press and a short haptic tick where the browser supports vibration, so a quick swipe scrolls between columns and a deliberate hold lifts the card. Hold a lifted card near the board's edge and the board scrolls sideways to the next column. The ⋯ button is always visible on touch, and Move to opens as a bottom sheet. The sensor setup is explained in the dnd kit mobile touch drag tutorial, and the sheet pattern in the React bottom sheet guide.

For a board that runs to the screen edges, pass the full-bleed classes the docs suggest, so the peeking column starts at the edge of the screen:

tsx
<Kanban columns={columns} value={deals} onValueChange={setDeals} classNames={{ nav: "-mx-4 px-4", board: "-mx-4 px-4 scroll-px-4" }} />

How do I make a kanban board accessible?

Give every move a keyboard path, a menu path and a spoken announcement. Every card is a Tab stop. Space picks it up, Up and Down move it within its column, Left and Right jump to the nearest spot in the neighboring column, Space or Enter drops it, and Escape puts it back. Enter on a resting card runs onCardClick. Shift + F10 or the Menu key opens the card's ⋯ menu, which is built on the Dropdown Menu; add your own entries with getCardActions and column entries with getColumnActions.

A screen reader hears each step from live announcements such as "Northwind Logistics moved to Negotiation, position 2 of 4". All strings, including those announcements, live in labels and merge over DEFAULT_KANBAN_LABELS, so you can translate the board without forking it. Give cards with a non-string title a textValue so menus and announcements have plain text to read. Reduced motion drops the tilt, the pulse and the sliding.

What are the common React kanban board pitfalls?

Four mistakes show up once a board moves from demo data to real data.

  • No height on the container. The board fills its parent. Without a height, columns grow with their cards and the page scrolls instead of the column.
  • A card with an unknown columnId. It is not rendered, and a development warning names it. Check this when column ids come from a database.
  • Dragging as the only way to move. Keep Move to in the menu (it is there unless readOnly), or the board no longer meets WCAG 2.5.7.
  • A board for a long list. A column with hundreds of cards reads better as a table with a status filter, which turns into cards on phones (responsive table pattern).

When should I use the Deal Pipeline instead?

Use it when the board is a sales pipeline. The Deal Pipeline is built on the Kanban and adds stage probabilities, total and weighted total per stage, a forecast summary with the win rate, and a reason dialog when a deal is dropped on Lost. onDealMove can return a promise: the card shows a spinner while it saves and springs back if the save fails. It also has a list view and an owner filter.

Drag a deal to Lost: a dialog asks why before the move commits (on a phone, use ⋯ then Mark as lost). Switch to List in the toolbar to see the same deals grouped by stage
$ npx wingo-ui@latest add deal-pipeline
ProDeal Pipeline docs

How do I add it to my project?

Run npx wingo-ui@latest add kanban (or deal-pipeline). The CLI copies the source into components/ui, adds the components, hooks and helpers it imports, and installs @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities, motion and lucide-react. Both are Pro components (pricing). More single-component deep dives are in the component guides category, and the React form components guide applies the same touch, keyboard and screen reader rules to every form control.

Components in this post

  • Kanban

    A React kanban board for tasks, orders and pipelines: drag cards with mouse, touch or keyboard, WIP limits, inline add and a phone layout.

    Pro
  • Deal Pipeline

    A React sales pipeline board: deal stages with weighted totals, a forecast summary, drag between stages with a lost reason, a list view.

    Pro

FAQ

What is the best React kanban library?

It depends on what you need around the board. A source component built on dnd kit, such as the Dice UI or Wingo UI kanban, gives you code you can edit; a packaged library such as SVAR React Kanban gives you a configured widget. Check touch drag, keyboard moves and a phone layout before you pick, because many boards only demo on a desktop.

How do WIP limits work on a kanban board?

A WIP limit is the most cards a column should hold at once. Tools either warn (Jira colors a column red when it passes its maximum) or block the drop until a card leaves; the Wingo UI Kanban supports both through limitBehavior="warn" or "block".

How do I make a kanban board work on mobile?

Show one column at a time with the next one peeking, snap between columns, and add a row of chips to jump to a column. Lift cards with a long press so a swipe still scrolls, and offer a Move to menu in a bottom sheet for people who do not want to drag.

Is there a shadcn kanban component?

shadcn/ui itself does not ship a kanban board. Third-party registries do: Dice UI installs one through the shadcn CLI, and the Wingo UI Kanban installs as source with npx wingo-ui@latest add kanban.

Is the Wingo UI Kanban free?

No. The Kanban and the Deal Pipeline are Pro components. The Empty State it uses for empty columns is free.

  • Kanban
  • Drag and drop
  • Mobile UI
  • Accessibility
  • React

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

dnd kit Mobile: Touch Drag That Lets the Page Scroll

Fix dnd kit mobile drag: a TouchSensor long press that still lets the page scroll, touch-action per element, the iOS Safari resize cancel and a tap path.

SRSerban Rusu·Oct 9, 2026·11 min read
Build it: screens and apps

React CRM Template: Pipeline, Forecast, Company Pages

Build a React CRM template in Next.js from blocks: a deal pipeline kanban with weighted forecasts, a deal sheet, company record pages and a phone layout.

SRSerban Rusu·Oct 16, 2026·11 min read
Component guides

Embla Carousel React: Accessible Slides, No Layout Shift

Build an embla carousel react component for photos, listings, testimonials and onboarding, with keyboard controls, pausable autoplay and no layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI