Zephora UI

Data

Kanban

Kanban board with columns of draggable cards — pointer drag & drop, a fully keyboard-accessible move mode with live announcements, WIP-limit marking, locked columns, per-target drop rules and optional column reordering.

Import

import { Kanban } from "@zephora/react";

Examples

Basic

Uncontrolled board via `defaultColumns`. `onCardMove` receives the move delta after a card lands somewhere new.

Press Space to pick up, arrow keys to move, Space to drop, Escape to cancel
To do3
Design tokens audit
Dark mode QA pass
Docs search index
In progress2
DataTable virtualization
Calendar week view
Done1
Keyboard drag & drop
Drag a card, or focus one and press Space.

Rules and limits

The locked `Released` column rejects every drop (cards can still be dragged out). `In progress` has a WIP limit of 2 — exceeding it marks the column with `data-over-limit` — and `canDropCard` only lets cards flagged ready enter it.

Press Space to pick up, arrow keys to move, Space to drop, Escape to cancel
Backlog3
Fix focus ring
New landing page
Refactor theming
In progress2/2
RTL audit
Perf budget CI
Released1
v2.3.0

Custom cards

`renderCard` replaces the card body; the arbitrary `data` payload travels with the card through every move.

Press Space to pick up, arrow keys to move, Space to drop, Escape to cancel
To do2
Ship calendar docs
ALdocs
Sparkline gradients
GHfeature
In review1
Tooltip delay tuning
ATbug
Done1
Chip overflow fix
BLbug

API

Kanban props

PropTypeDefaultDescription
columnsKanbanColumnData[]—Controlled board state.
defaultColumnsKanbanColumnData[][]Uncontrolled initial board state.
onColumnsChange(columns: KanbanColumnData[]) => void—Fires with the full next columns array after any card / column move.
onCardMove(detail: KanbanCardMoveDetail) => void—Fires with the move delta after a card lands somewhere new.
onColumnMove(columnId: string, fromIndex: number, toIndex: number) => void—Fires after a column is reordered (requires `reorderableColumns`).
reorderableColumnsbooleanfalseMakes column headers draggable to reorder columns.
isCardDraggable(card: KanbanCard, column: KanbanColumnData) => boolean—Per-card drag opt-out (disabled cards are never draggable).
canDropCard(card: KanbanCard, target: KanbanColumnData) => boolean—Per-target drop rule; locked columns are always rejected.
onCardClick(card: KanbanCard, column: KanbanColumnData, event: MouseEvent) => void—Click handler for enabled cards.
renderCard(card, { column, dragging }) => ReactNode—Custom card body renderer.
renderColumnHeader(column: KanbanColumnData) => ReactNode—Custom column header renderer (replaces title + count badge).
unstyledbooleanfalseHeadless mode — skips Zephora styling.
classNamesPartial<Record<KanbanSlot, string>>—Per-slot class overrides, appended after the module classes; also applied in unstyled mode.

KanbanColumnData props

PropTypeDefaultDescription
id *string—Unique column id.
title *ReactNode—Column header title.
cards *KanbanCard[]—Cards in display order.
lockedboolean—Locked columns reject drops (cards can still be dragged out).
limitnumber—WIP limit; exceeding it only marks the column with `data-over-limit`.

KanbanCard props

PropTypeDefaultDescription
id *string—Unique card id.
titleReactNode—Default card content (falls back to `id`).
dataunknown—Arbitrary consumer payload carried with the card.
disabledboolean—Disabled cards cannot be dragged, picked up or clicked.

KanbanCardMoveDetail props

PropTypeDefaultDescription
cardIdstring—Id of the moved card.
fromColumnIdstring—Source column id.
toColumnIdstring—Target column id.
fromIndexnumber—Index the card left in the source column.
toIndexnumber—Final index of the card inside the target column.
method"pointer" | "keyboard"—Whether the move came from drag & drop or the keyboard move mode.

Keyboard

KeyAction
TabMoves focus into / out of the board (one card is tabbable at a time).
Arrow keysMove focus between cards — up / down within a column, left / right to the neighbor column.
Enter / SpacePicks up the focused card (enters move mode).
Arrow keys (while picked)Move the pick-up target — up / down within the column, left / right to the nearest column that accepts the card.
Enter / Space (while picked)Drops the card at the announced position.
EscapeCancels the move and keeps the card in place.