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[]) => voidFires with the full next columns array after any card / column move.
onCardMove(detail: KanbanCardMoveDetail) => voidFires with the move delta after a card lands somewhere new.
onColumnMove(columnId: string, fromIndex: number, toIndex: number) => voidFires after a column is reordered (requires `reorderableColumns`).
reorderableColumnsbooleanfalseMakes column headers draggable to reorder columns.
isCardDraggable(card: KanbanCard, column: KanbanColumnData) => booleanPer-card drag opt-out (disabled cards are never draggable).
canDropCard(card: KanbanCard, target: KanbanColumnData) => booleanPer-target drop rule; locked columns are always rejected.
onCardClick(card: KanbanCard, column: KanbanColumnData, event: MouseEvent) => voidClick handler for enabled cards.
renderCard(card, { column, dragging }) => ReactNodeCustom card body renderer.
renderColumnHeader(column: KanbanColumnData) => ReactNodeCustom 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 *stringUnique column id.
title *ReactNodeColumn header title.
cards *KanbanCard[]Cards in display order.
lockedbooleanLocked columns reject drops (cards can still be dragged out).
limitnumberWIP limit; exceeding it only marks the column with `data-over-limit`.

KanbanCard props

PropTypeDefaultDescription
id *stringUnique card id.
titleReactNodeDefault card content (falls back to `id`).
dataunknownArbitrary consumer payload carried with the card.
disabledbooleanDisabled cards cannot be dragged, picked up or clicked.

KanbanCardMoveDetail props

PropTypeDefaultDescription
cardIdstringId of the moved card.
fromColumnIdstringSource column id.
toColumnIdstringTarget column id.
fromIndexnumberIndex the card left in the source column.
toIndexnumberFinal 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.