Build an Arrow-Controlled Kanban Board in React
SnowflakeFrontend Engineer · Technical ScreenLast 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
-
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
idalongside ordered lists of task IDs per column) to ensure constant-time lookups and efficient re-renders.
- Columns are ordered left-to-right based on an array of column definitions (e.g.,
-
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.
- Each task card must have a "Move Left" button (
-
Accessibility (a11y):
- Buttons must include meaningful
aria-labelattributes (e.g.,Move "Implement Auth" to In Progress). - Columns should be semantic landmarks or lists (`` or
/).
- Buttons must include meaningful
-
Performance Consideration:
- Updates to move a single task should run in
O(1)time with respect to the total number of tasksNacross the board.
- Updates to move a single task should run in
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?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.