Back

Kanban Board (DnD)

Hard

Streak

0 days

Progress

0%

Submitted

0

Kanban Board (DnD)

React35 minHardFree

Prompt

A Kanban Board is the backbone of every project management tool — Trello, Jira, Linear, Notion, GitHub Projects all use this pattern. It's a visual system for tracking work items across columns that represent stages: To Do, In Progress, Done. Your task is to build a drag-and-drop Kanban board in React using the native HTML5 Drag and Drop API. This question is assigned to test your knowledge of the HTML5 drag-and-drop event model — a browser-native API with specific events that must fire in a particular sequence. Most candidates reach for a library (react-dnd, @dnd-kit) without understanding the underlying mechanics. Knowing how to implement it with native browser APIs demonstrates a deeper understanding of the web platform and makes you independent of third-party dependencies. Your implementation should allow cards to be dragged from one column and dropped into another, highlight the target column during a drag-over, and update state immutably when a drop occurs.

Requirements

  • →Three columns: To Do, In Progress, Done — each with a list of cards
  • →Cards can be dragged from any column and dropped into any other
  • →Target column is visually highlighted when a card is being dragged over it
  • →State updates correctly when a card is dropped: removed from source column, added to target column
  • →Dropping in the same column is a no-op
  • →Board data initialized from a structured data object
  • →Drag handle visual cue on each card
Example
Loading preview...
For the best coding experience, we recommend using a desktop device.
Preparing Sandbox...
Premium interview report

What interviewers score in this build

Use this before reading the code. It tells you what to say, what to test, and where machine-coding candidates usually lose points.

Interview signals

  • React: Board state is a nested object: {[colId]: {label, cards: [{id, text}]}}. dragItem ref holds {cardId, sourceColId}. dragOver state tracks which column is highlighted. Immutable state update on drop.
  • HTML/CSS: draggable attribute on card divs. dragstart, dragover, dragleave, drop handlers. e.preventDefault() in dragover. Visual highlight on dragOver column. cursor:grab on cards.
  • Component Architecture: Board state as a single nested object. Column rendering by Object.entries(board). Card draggability fully driven by event handlers on card and column divs.
  • State Management: board object holds all columns and cards. dragOver state is only for visual feedback — reset on drop/dragleave. dragItem ref (not state) stores what's being dragged — no re-render needed on drag start.

Time checkpoints

  1. 1

    0:00: Read prompt. Note: native HTML5 DnD. Key events: dragstart, dragover (needs preventDefault), drop. Plan board state shape.

  2. 2

    4:00: Initialize board state. Render columns and cards. Add draggable attribute to cards.

  3. 3

    10:00: Implement dragstart handler — save cardId + sourceColId to ref.

  4. 4

    15:00: Implement dragover (with e.preventDefault) and drop handlers. Move card in state.

Edge-case checklist

Empty data and first-load state
Slow network, failed request, and retry path
Keyboard navigation and focus movement
Large input size, re-render pressure, and cleanup

Common mistakes

  • Starting with JSX before naming state and events.
  • Ignoring accessibility until the final minute.
  • Over-building abstractions instead of finishing the required behavior.
  • Failing to narrate trade-offs while coding.
SolutionRead-only · Live Preview

Technical Explanation

Problem Understanding

Most candidates attempt Kanban with a drag-and-drop library. That's a valid production choice, but in interviews it signals you don't understand the underlying API. The interviewer wants to see you implement it with native browser APIs. This demonstrates depth of platform knowledge that library usage hides.

The HTML5 Drag and Drop event sequence on a drag operation:

  1. dragstart — fires on the source element (the card) when drag begins
  2. dragover — fires repeatedly on the target element (the column) as you drag over it
  3. dragleave — fires when you leave the target element
  4. drop — fires on the target element when you release
  5. dragend — fires on the source element after drop
⚠️ Critical Rule: You MUST call e.preventDefault() in the dragover handler. Without it, the browser treats the target as "not a valid drop zone" and the drop event will never fire. This is the single most common reason native DnD doesn't work.

State Shape

const [board, setBoard] = useState({
  todo: {
    label: 'To Do',
    cards: [
      { id: '1', text: 'Design the API' },
      { id: '2', text: 'Write tests' },
    ],
  },
  inprogress: {
    label: 'In Progress',
    cards: [{ id: '3', text: 'Build kanban board' }],
  },
  done: {
    label: 'Done',
    cards: [{ id: '4', text: 'Project setup' }],
  },
});

The dragItem Ref — Why Not State?

const dragItem = useRef(null); // { cardId, sourceColId }

function handleDragStart(e, cardId, sourceColId) {
  dragItem.current = { cardId, sourceColId };
  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/plain', ''); // required for Firefox
}

Using a ref (not state) here is intentional. We don't want drag start to trigger a re-render — the component looks the same while a card is being picked up. We just need to remember which card we're dragging for when the drop fires. A ref gives us mutable storage without a re-render.

The Drop Handler — Immutable State Update

function handleDrop(targetColId) {
  setDragOver(null);
  const { cardId, sourceColId } = dragItem.current;

  if (sourceColId === targetColId) return; // no-op: same column

  setBoard(prev => {
    const sourceCards = prev[sourceColId].cards;
    const card = sourceCards.find(c => c.id === cardId);

    return {
      ...prev,
      // Remove from source
      [sourceColId]: {
        ...prev[sourceColId],
        cards: sourceCards.filter(c => c.id !== cardId),
      },
      // Add to target
      [targetColId]: {
        ...prev[targetColId],
        cards: [...prev[targetColId].cards, card],
      },
    };
  });

  dragItem.current = null;
}

This is a single atomic state update — both the removal from source and addition to target happen in one setBoard call. Never mutate the arrays in place.

Column Highlight During Drag

const [dragOver, setDragOver] = useState(null);

// On column div:
onDragOver={e => { e.preventDefault(); setDragOver(colId); }}
onDragLeave={() => setDragOver(null)}
onDrop={() => handleDrop(colId)}

// CSS:
className={`column ${dragOver === colId ? 'column-highlight' : ''}`}

Common Pitfalls Summary

  • Missing e.preventDefault() in dragover: Drop event never fires.
  • Using state for dragItem: Causes an unnecessary re-render when drag starts. Use a ref.
  • Mutating the cards array: board.todo.cards.push(card) breaks React's immutability model. Use filter and spread.
  • Not checking sourceColId === targetColId: Dropping on the same column removes the card and adds it back — unnecessary and visually glitchy.
  • Not calling dataTransfer.setData() in dragstart: Firefox requires this call even if you don't use the data transfer value. Omitting it breaks drag in Firefox.

Interview Criteria

React

Board state is a nested object: {[colId]: {label, cards: [{id, text}]}}. dragItem ref holds {cardId, sourceColId}. dragOver state tracks which column is highlighted. Immutable state update on drop.

HTML/CSS

draggable attribute on card divs. dragstart, dragover, dragleave, drop handlers. e.preventDefault() in dragover. Visual highlight on dragOver column. cursor:grab on cards.

Component Architecture

Board state as a single nested object. Column rendering by Object.entries(board). Card draggability fully driven by event handlers on card and column divs.

State Management

board object holds all columns and cards. dragOver state is only for visual feedback — reset on drop/dragleave. dragItem ref (not state) stores what's being dragged — no re-render needed on drag start.

Edge Cases

Drop in same column is a no-op (sourceColId === targetColId check). dragLeave resets dragOver highlight. dataTransfer.setData required for Firefox compatibility. Column shows empty state when it has no cards.

Time Checkpoints

0:00

0:00: Read prompt. Note: native HTML5 DnD. Key events: dragstart, dragover (needs preventDefault), drop. Plan board state shape.

4:00

4:00: Initialize board state. Render columns and cards. Add draggable attribute to cards.

10:00

10:00: Implement dragstart handler — save cardId + sourceColId to ref.

15:00

15:00: Implement dragover (with e.preventDefault) and drop handlers. Move card in state.

20:00

20:00: Add dragOver state for column highlight. Add dragleave to clear it.

26:00

26:00: Handle same-column drop no-op. Add empty column state. Polish card design.

Streak

0 days

Last active: Sign in to track

Progress

0%

0/0 solved

Submitted

0

Solutions pushed to review history.