| title | Plugin Kanban |
|---|
import { InteractiveDemo } from '@/app/components/InteractiveDemo'; import { PluginLoader } from '@/app/components/PluginLoader';
Kanban board component with drag-and-drop powered by @dnd-kit.
npm install @object-ui/plugin-kanban<PluginLoader plugins={['kanban']}>
// Import once in your app entry point
import '@object-ui/plugin-kanban'
// Use in schemas
const schema = {
type: 'kanban',
columns: [
{
id: 'todo',
title: 'To Do',
cards: [
{ id: '1', title: 'Task 1', description: 'Do something' }
]
},
{
id: 'done',
title: 'Done',
cards: []
}
],
onCardMove: (cardId, fromCol, toCol, index) => {
console.log(`Card ${cardId} moved`)
}
}- Drag and drop cards between columns
- Column limits (WIP limits)
- Card badges for status/priority
- Keyboard navigation
- Lazy-loaded (~100-150 KB loads only when rendered)
{
type: 'kanban',
columns?: KanbanColumn[],
onCardMove?: (cardId, fromColumnId, toColumnId, newIndex) => void,
className?: string
}
interface KanbanColumn {
id: string
title: string
cards: KanbanCard[]
limit?: number // Max cards allowed
className?: string
}
interface KanbanCard {
id: string
title: string
description?: string
badges?: Array<{
label: string
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
}>
}
| Property | Type | Description |
|---|---|---|
columns |
KanbanColumn[] | Array of column definitions |
onCardMove |
function | Callback when a card is moved |
className |
string | Additional Tailwind CSS classes |
| Property | Type | Description |
|---|---|---|
id |
string | Unique column identifier |
title |
string | Column title |
cards |
KanbanCard[] | Cards in this column |
limit |
number | Max cards allowed (WIP limit) |
className |
string | Additional Tailwind CSS classes |
| Property | Type | Description |
|---|---|---|
id |
string | Unique card identifier |
title |
string | Card title |
description |
string | Card description |
badges |
Badge[] | Status/priority badges |
When a board is bound to an object (an object-kanban, or a kanban view inside
ListView / ObjectView) instead of being given static columns, the fields
rendered on each card resolve in priority order:
- View-level
cardFields— the fields the view configures (a kanban view'scolumns, forwarded ascardFields). An explicit choice always wins. - The object's
highlightFields— the object's ADR-0085 semantic role (its curated "most important fields"), the same list the Grid, List and Detail surfaces already default to. Used when the view configures no card fields, so a board over an object with no per-view config still surfaces meaningful fields. Entries that reference a field the object no longer declares are ignored. - A legacy semantic-field heuristic — a best-effort guess (amount, owner, priority, …) used only when neither of the above is available.
Defaulting to highlightFields keeps a card's contents consistent with the
object's other views without every kanban view having to re-declare its fields.
const taskBoard = {
type: 'kanban',
columns: [
{
id: 'backlog',
title: 'Backlog',
cards: [
{
id: 'task-1',
title: 'Design new homepage',
description: 'Create mockups for the new landing page',
badges: [
{ label: 'Design', variant: 'default' },
{ label: 'High Priority', variant: 'destructive' }
]
}
]
},
{
id: 'in-progress',
title: 'In Progress',
limit: 3, // WIP limit
cards: [
{
id: 'task-2',
title: 'Implement authentication',
description: 'Add OAuth2 login flow',
badges: [
{ label: 'Backend', variant: 'secondary' }
]
}
]
},
{
id: 'review',
title: 'Code Review',
cards: []
},
{
id: 'done',
title: 'Done',
cards: []
}
],
onCardMove: (cardId, fromCol, toCol, index) => {
// Update backend/state
console.log(`Moved ${cardId} from ${fromCol} to ${toCol}`)
}
}const ticketBoard = {
type: 'kanban',
columns: [
{
id: 'new',
title: 'New Tickets',
cards: [
{
id: 'ticket-1',
title: 'Login not working',
description: 'User cannot log in with Google',
badges: [
{ label: 'Bug', variant: 'destructive' },
{ label: 'P1', variant: 'destructive' }
]
}
]
},
{
id: 'assigned',
title: 'Assigned',
limit: 5,
cards: []
},
{
id: 'resolved',
title: 'Resolved',
cards: []
}
],
className: 'min-h-[600px]'
}const salesPipeline = {
type: 'kanban',
columns: [
{
id: 'leads',
title: 'Leads',
cards: [
{
id: 'lead-1',
title: 'Acme Corp',
description: '$50,000 - Enterprise plan',
badges: [
{ label: 'Hot Lead', variant: 'destructive' }
]
}
]
},
{
id: 'qualified',
title: 'Qualified',
cards: []
},
{
id: 'proposal',
title: 'Proposal Sent',
cards: []
},
{
id: 'won',
title: 'Won',
cards: []
}
]
}Use badges to show card status, priority, or categories:
const card = {
id: 'task-1',
title: 'Important Task',
badges: [
{ label: 'Frontend', variant: 'default' },
{ label: 'Urgent', variant: 'destructive' },
{ label: 'Reviewed', variant: 'secondary' },
{ label: 'Blocked', variant: 'outline' }
]
}default- Blue badgesecondary- Gray badgedestructive- Red badgeoutline- Outlined badge
Set maximum cards per column to enforce work-in-progress limits:
const column = {
id: 'in-progress',
title: 'In Progress',
limit: 3, // Max 3 cards
cards: [...]
}When the limit is reached, the column shows visual feedback.
Handle card movements to update your backend or state:
const schema = {
type: 'kanban',
columns: [...],
onCardMove: (cardId, fromColumnId, toColumnId, newIndex) => {
// Update database
await updateCardColumn(cardId, toColumnId, newIndex)
// Update local state
setCards(prev => moveCard(prev, cardId, toColumnId, newIndex))
}
}The plugin uses lazy loading to optimize bundle size:
- Initial load: ~0.2 KB (entry point)
- Lazy chunk: ~100-150 KB (loaded when kanban is rendered)
- Includes @dnd-kit for drag-and-drop functionality
The kanban board includes:
- Keyboard navigation - Move cards with keyboard
- Screen reader support - ARIA labels for all interactions
- Focus management - Clear focus indicators
import type { KanbanSchema, KanbanCard, KanbanColumn } from '@object-ui/plugin-kanban'
const column: KanbanColumn = {
id: 'todo',
title: 'To Do',
cards: [],
limit: 5
}
const kanbanSchema: KanbanSchema = {
type: 'kanban',
columns: [column]
}