Katkat

Build an Arrow-Controlled Kanban Board in React

SnowflakeFrontend Engineer · Technical Screen

Last updated

Design and implement a performant React Kanban board in TypeScript with normalized state and accessible arrow controls for shifting tasks across ordered columns at scale.

Problem Statement

Build a React component KanbanBoard that renders tasks grouped into ordered status columns. Instead of drag-and-drop, users reassign task statuses using accessible arrow buttons on each task card.

Requirements

  1. Columns & State Representation:

    • Columns are ordered left-to-right based on an array of column definitions (e.g., ["Backlog", "In Progress", "Review", "Done"]).
    • Tasks must be managed using a normalized state structure (such as an entity dictionary mapped by id alongside ordered lists of task IDs per column) to ensure constant-time lookups and efficient re-renders.
  2. Task Movement:

    • Each task card must have a "Move Left" button (←) and a "Move Right" button (→).
    • Clicking "Move Left" shifts the task to the immediately preceding column.
    • Clicking "Move Right" shifts the task to the immediately succeeding column.
    • Moving a task appends it to the bottom of the destination column's list.
    • The "Move Left" button must be disabled or hidden if the task is already in the leftmost column. Similarly, "Move Right" must be disabled or hidden if in the rightmost column.
  3. Accessibility (a11y):

    • Buttons must include meaningful aria-label attributes (e.g., Move "Implement Auth" to In Progress).
    • Columns should be semantic landmarks or lists (`` or /).
  4. Performance Consideration:

    • Updates to move a single task should run in O(1) time with respect to the total number of tasks N across the board.

Component Interface

export interface Task {
  id: string;
  title: string;
  columnId: string;
}

export interface Column {
  id: string;
  title: string;
}

export interface KanbanBoardProps {
  initialColumns: Column[];
  initialTasks: Task[];
  onTaskMove?: (taskId: string, sourceColId: string, destColId: string) => void;
}

export const KanbanBoard: React.FC = ({ ... }) => {
  // Implementation here
};

Example

const columns = [
  { id: 'todo', title: 'To Do' },
  { id: 'in_progress', title: 'In Progress' },
  { id: 'done', title: 'Done' }
];

const tasks = [
  { id: '1', title: 'Setup Repo', columnId: 'todo' },
  { id: '2', title: 'Write Tests', columnId: 'todo' },
  { id: '3', title: 'Deploy', columnId: 'in_progress' }
];
  • Clicking → on task "1" removes "1" from "todo" and appends it to "in_progress" after "3".
  • In todo, task "2" now sits at the top.
  • In in_progress, task "1" has both ← (enabled) and → (enabled).

Edge Cases

  • Empty columns: Columns with zero tasks should display an empty state or allow incoming tasks smoothly.
  • Single column board: Both left and right buttons must remain disabled.
  • Rapid sequential clicks: State transitions must not drop movements or corrupt list indices.

Hints

Hint 1

Normalize your internal state into `byId: Record` and `columnTaskIds: Record` to avoid searching through all tasks on every move.

Hint 2

Use a lookup map for column indices (e.g., `colIndexMap: Record`) so you can determine the target column and disable boundary buttons in `O(1)` time.

Hint 3

When moving an item, perform an immutable array slice/filter on the source column's ID array and an immutable append on the destination column's ID array.

Follow-up questions

  • How would you integrate window virtualization (e.g., `@tanstack/react-virtual`) when a single column contains tens of thousands of tasks?
  • How would you support keyboard navigation (e.g., focusing a card and pressing arrow keys directly) while maintaining screen reader announcements via ARIA live regions?
  • If the board needs to sync with a remote backend, how would you implement optimistic updates and rollback on network failure?