AI
Kanban logov1.0.0

Kanban Customization and Slots

Nofinite Kanban provides a flexible customization architecture allowing teams to customize card presentation, column headers, metadata badges, user avatars, and subtask progress indicators.

By default, cards render an accessible, high-density layout styled with NUICSS superclasses. You can replace default card bodies or column headers without breaking Pragmatic Drag-and-Drop hitboxes or keyboard navigation.

typescript
// Framework-Agnostic Core
import { createKanbanBoard, renderCardHTML } from '@nofinite/kanban';

// React Adapter
import { Kanban } from '@nofinite/kanban/react';

1. Framework-Agnostic Core Customization

In Vanilla JavaScript, TypeScript, or non-React web components, pass a custom renderCardHTML callback when creating the board:

typescript
import { createKanbanBoard } from '@nofinite/kanban';
import '@nofinite/nuicss/styles.css';

const board = createKanbanBoard(document.getElementById('board'), {
  columns: myColumns,
  // Custom HTML template for each task card
  renderCardHTML: (item, col, itemIndex, colIndex) => {
    return `
      <div class="p-3.5 space-y-2 bg-surface border border-default rounded-xl shadow-xs">
        <div class="flex items-center justify-between text-xs">
          <span class="badge badge-primary font-mono text-[10px]">${item.badge?.text || 'Task'}</span>
          <span class="text-muted font-mono text-[11px]">${item.dueDate || ''}</span>
        </div>
        <h4 class="text-sm font-semibold text-default">${item.title}</h4>
        ${item.description ? `<p class="text-xs text-muted line-clamp-2">${item.description}</p>` : ''}
      </div>
    `.trim();
  }
});

2. React Slot-Based Customization

Custom Card Slot (renderCard)

In React, use renderCard to render any custom JSX component inside the draggable card container:

tsx
import React, { useState } from 'react';
import { Kanban, type KanbanColumnProps, type KanbanItem } from '@nofinite/kanban/react';
import '@nofinite/nuicss/styles.css';

interface SprintTask extends KanbanItem {
  storyPoints?: number;
  priority?: 'critical' | 'high' | 'medium' | 'low';
}

export function CustomCardBoard() {
  const [columns, setColumns] = useState<KanbanColumnProps[]>(/* ... */);

  return (
    <div className="w-full h-[650px] p-6 bg-surface">
      <Kanban
        columns={columns}
        onChange={setColumns}
        renderCard={(item: SprintTask, columnId: string) => (
          <div className="p-3.5 space-y-2.5">
            {/* Header: Priority & Story Points */}
            <div className="flex items-center justify-between">
              <span
                className={`text-[10px] uppercase font-bold tracking-wider px-2 py-0.5 rounded-full ${
                  item.priority === 'high'
                    ? 'bg-danger/10 text-danger border border-danger/20'
                    : 'bg-primary/10 text-primary border border-primary/20'
                }`}
              >
                {item.priority}
              </span>
              {item.storyPoints && (
                <span className="text-xs font-mono font-semibold text-muted bg-subtle px-1.5 py-0.5 rounded">
                  {item.storyPoints} pts
                </span>
              )}
            </div>

            {/* Title & Description */}
            <div>
              <h4 className="text-sm font-semibold text-default leading-snug">{item.title}</h4>
              {item.description && (
                <p className="text-xs text-muted mt-1 line-clamp-2">{item.description}</p>
              )}
            </div>
          </div>
        )}
      />
    </div>
  );
}

Custom Column Header Slot (renderHeader)

Customize column headers to add custom metrics or quick action buttons:

tsx
<Kanban
  columns={columns}
  renderHeader={(col) => (
    <div className="flex items-center justify-between w-full py-2 px-3 border-b border-default">
      <span className="font-bold text-sm text-default">{col.title}</span>
      <span className="text-xs font-mono text-muted bg-subtle px-2 py-0.5 rounded">
        {col.items.length} / {col.wipLimit || '∞'}
      </span>
    </div>
  )}
/>