A lightweight, highly responsive, and accessible Kanban board built with React, TypeScript, and Vite. This project serves as a showcase for building performant user interfaces without relying on bloated external dependencies for state management, drag-and-drop, or heavy UI component libraries.
To get the Sprint Board up and running on your local machine, follow these steps:
-
Navigate to the directory:
cd sprint-board -
Install dependencies:
npm install
(Note: This project strictly uses core React dependencies without bulky external libraries!)
-
Start the development server:
npm run dev
-
View the app: Open http://localhost:5173 in your browser.
The application follows a strict top-down data flow. The Board component acts as the single source of truth for the task data, automatically computing derived states for filtering and searching.
graph TD
AppShell[AppShell] --> Board[Board]
Board -->|Filters & Search| Controls[SearchBar & FilterBar]
Board -->|Passes filtered tasks| Column[Column]
Column -->|Renders| TaskCard[TaskCard]
Board -.->|Manages State| TaskForm[TaskForm Modal]
Hooks[[Custom Hooks: useLocalStorage, useSeedData]] -.->|Persists Data| Board
During the development of this application, several deliberate architectural choices were made to prioritize performance, maintainability, and clean code:
To keep the footprint incredibly small and avoid relying on external libraries, this project utilizes native CSS Modules. This approach guarantees that styles are strictly scoped to their respective components (eliminating global CSS bleeding and specificity wars) while maintaining highly readable markup in the TSX files. It proves that a beautiful, modern, native-feeling UI (complete with responsive CSS scroll-snapping) can be built cleanly using fundamental web technologies.
Rather than passing state down and having each Column component independently manage its own array of tasks, the Board component acts as the absolute single source of truth. The tasks array lives at the top level and is backed by a custom useLocalStorage hook.
- Filtering & Searching: Instead of manually syncing multiple state arrays when a search or filter occurs, we use
useMemoto compute a derivedfilteredTasksarray. - Why? This completely eliminates the entire class of bugs where columns get out of sync with each other. It also dramatically simplifies the CRUD handlers (
addTask,editTask,deleteTask,moveTask), which only ever need to mutate the master list.
When fetching initial seed data from the JSONPlaceholder API, the mapping function generates priorities (Low, Medium, High) and Assignees deterministically using modulo math on the Task ID (e.g., todo.id % priorities.length) rather than utilizing Math.random().
- Why? Relying on
Math.random()can cause hydration mismatches, unpredictable testing environments, and jarring UI shifts if the function is ever re-evaluated. Using deterministic modulo math ensures that "Task 4" will always look mathematically identical across every refresh and every device.
Because this project was scoped to be built rapidly and strictly without external libraries, there are several features knowingly left out that would be prioritized given more time:
- Drag and Drop (DnD): Currently, tasks are moved between columns using a highly accessible
<select>dropdown. A robust implementation would eventually introduce an accessibility-first library like@dnd-kit/coreto allow physical dragging ofTaskCards between columns. - Undo functionality & Toast Notifications: If a user accidentally deletes a task right now, it is permanently purged from
localStorage. Implementing a short-lived "Undo" toast notification backed by a simple state history stack would be a massive UX improvement. - Automated Testing: There is currently no unit testing (
Vitest/React Testing Library) or E2E testing (Playwright/Cypress) implemented. Adding strict tests for the derived filtering logic (making sure filters properly stack with AND logic) and theuseLocalStoragehook would be the immediate next step before a production release. - State Management Context: As the app grows and more layers are added, drilling callback props (
onMove,onDelete,onEdit) down fromBoardtoColumntoTaskCardwill become tedious. Wrapping the core state in a ReactContext(or adopting a lightweight store likeZustand) would make the architecture infinitely more scalable.