Kanban Board

ReactMedium60 minSnowflakeNavi

Prompt

Design and implement a fully functional Kanban Board to visualize and manage a project's workflow. The board should consist of four columns representing different task stages. Your goal is to enable users to move tasks sequentially through these stages using a simple button-based interface.

Requirements

  • Create a 4-column board: "Todo", "In Progress", "In Review", "Complete".
  • Display cards in each column with a title and navigation buttons.
  • Implement functionality to move cards between adjacent columns.
  • Ensure cards cannot be moved beyond the first or last columns.
  • The board should be responsive and accessible.

Note

  • A Card component is provided to handle the UI and button clicks.
  • You don't need to worry about drag-and-drop; button navigation is sufficient.
  • Focus on designing a clean state structure to manage the cards and columns.

Example

Kanban Board

Playground

Hint 1

Think about how you want to store the state. You could have an array of columns, each with an array of cards. OR, you could have a single flat array of cards, where each card has a columnIndex property. Which one is easier to manage when moving cards?

Hint 2

If you choose the flat array approach, you can render the columns by filtering the cards list for each column index:

{COLUMNS.map((columnName, index) => (
<div className="column">
<h2>{columnName}</h2>
{cards
.filter(card => card.columnIndex === index)
.map(card => <Card ... />)
}
</div>
))}
Hint 3

For proper accessibility, ensure each column and card has appropriate ARIA labels. Screen readers need to know what's happening!

<div
role="region"
aria-label={`${columnName} column`}
>
{/* ... */}
</div>

Solution

Explanation

Let's break down how to tackle this problem effectively. This isn't just about moving divs around; it's about state management and clean component architecture.

1. State Management: The "Flat" Approach

The most critical decision you'll make is how to structure your state. You might be tempted to create an array of columns, where each column has its own array of cards. While that mirrors the visual layout, it makes moving cards a pain. You have to find the card in one array, remove it, find the other array, and add it. That's a lot of friction.

Instead, Normalize your state. Keep a single flat list of all cards, and give each card a columnIndex property.

const [cards, setCards] = useState([
{ id: '1', title: 'Task A', columnIndex: 0 },
{ id: '2', title: 'Task B', columnIndex: 1 },
// ...
]);

Now, moving a card is incredibly simple. You just update one number:

const moveCard = (cardId, direction) => {
setCards(prevCards => prevCards.map(card => {
if (card.id === cardId) {
return { ...card, columnIndex: card.columnIndex + direction };
}
return card;
}));
};

No splicing, no finding indices, no complex logic. Just columnIndex + direction.

2. Rendering: Filter and Map

Since our state is flat, how do we render columns? We simply filter! We iterate through our column names, and for each column, we filter the master list of cards.

{COLUMNS.map((columnName, index) => (
<div className="column">
<h2>{columnName}</h2>
{cards
.filter(card => card.columnIndex === index)
.map(card => <Card ... />)
}
</div>
))}

This pattern is powerful because your UI is now a direct reflection of your state. There's no risk of "sync" issues where a card exists in two columns or disappears entirely.

3. Component Architecture

Notice how we separated the Card component? The Card component is "dumb"—it just takes props and renders. It doesn't know about the global state or how to update it. It just calls onMoveLeft or onMoveRight when clicked. The KanbanBoard is "smart"—it holds the state and defines the logic. This separation of concerns makes your code easier to read, test, and maintain.

4. Accessibility and UX

Don't forget the users who can't see the screen.

  • Semantic HTML: We used h2 for column titles.
  • ARIA Labels: We added aria-label to regions so screen readers know which column is which.
  • Button States: We conditionally render buttons. If you're in the first column, you can't go left. If you're in the last, you can't go right. This prevents errors and guides the user.

Common Pitfalls

Common Pitfalls

  1. Deeply Nested State: Trying to manage columns[i].cards[j] usually leads to complex, buggy code. Keep it flat!
  2. Mutating State: Never do card.columnIndex++. Always create a new object: return { ...card, columnIndex: ... }.
  3. Index as Key: Don't use the array index as a key for your cards. If the list changes, React will get confused. Use a unique id.
  4. Hardcoding Columns: Avoid hardcoding column logic inside the render loop. Use constants or configuration arrays so you can easily add a 5th column later if needed.

Interviewer Criteria

HTML/CSS

  • Did I use Flexbox or Grid to create the column layout?

  • Is the design responsive (stacking columns on mobile)?

  • Did I use semantic elements (h2, button) instead of just divs?

  • Are my CSS class names descriptive and meaningful?

JavaScript

  • Did I choose a flat state structure for easier management?

  • Is the moveCard logic clean and immutable?

  • Did I properly handle the edge cases (first and last columns)?

  • Are my functions well-organized and maintainable?

React

  • Did I use useState effectively for managing cards?

  • Did I separate the Card component to keep the code organized?

  • Did I avoid mutating the state directly?

  • Did I use unique keys for list items (not array index)?

Component Architecture

  • Did I effectively use the provided Card component?

  • Is the code well-organized and maintainable?

  • Did I follow React best practices?

Accessibility

  • Are the buttons accessible via keyboard?

  • Did I use aria-label or other ARIA attributes where necessary?

  • Is the navigation clear and accessible?

Time Checkpoints

  • 10:00 AM

    Interview starts

  • 10:03 AM

    Read prompt & clarify requirements (4 columns?)

  • 10:05 AM

    Setup state structure (Flat vs Nested)

  • 10:10 AM

    Implement basic rendering loop

  • 10:20 AM

    Implement moveCard logic

  • 10:30 AM

    Style the board and cards

  • 10:40 AM

    Refine accessibility & edge cases

  • 10:50 AM

    Final review & questions

00:00