The Question
FE DesignDesign a High-Performance Draggable Kanban Board
Design a frontend architecture for a Trello-like Kanban board that supports drag-and-drop interactions across multiple columns. Your solution should address smooth 60fps rendering during movement, robust collision detection algorithms to determine drop targets, and a state management strategy that handles optimistic updates and persistence. Please discuss how you would handle cross-device compatibility (Mouse vs. Touch) and accessibility for users relying on screen readers or keyboard navigation.
React
TypeScript
Pointer Events API
CSS Transforms
ARIA Live Regions
Optimistic UI
Context API
Questions & Insights
Clarifying Questions
What is the primary use case for this DND interface? (e.g., Trello-style Kanban board, layout builder, or file uploader).
Assumption: A Kanban board (Trello-like) is the target MVP, focusing on reordering items within lists and moving items between lists.
Do we need to support mobile/touch devices?
Assumption: Yes, the system must use Pointer Events or a polyfill to ensure cross-platform compatibility (Mouse + Touch).
What is the scale of the data? (e.g., 10 items vs 10,000 items).
Assumption: Maximum 50 items per list and 10 lists (500 items total), allowing us to avoid complex virtualization for the MVP.
Is multi-user real-time collaboration a requirement?
Assumption: No, the MVP focuses on a single-user experience with local persistence and optimistic UI updates.
Crash Strategy
The core bottleneck in DND systems is state synchronization and layout jitter. To solve this, we will follow this logical progression:
Geometric Tracking: How do we track the coordinates of the "active" item relative to "droppable" targets?
Visual Feedback: How do we manage the "ghost" element and placeholders without triggering heavy re-renders?
Collision Detection: How do we efficiently calculate which list or position the item is hovering over (Rect intersection)?
Data Reconciliation: How do we update the underlying data structures (arrays) and persist changes after the "drop"?
Elite Bonus Points
Sensor Abstraction: Decouple input logic (Mouse, Touch, Keyboard) from the DND engine to support "Keyboard DND" for accessibility.
Optimistic UI with Revert: Implementing a transaction-based state update that rolls back if the server-side persistence fails.
Collision Algorithms: Moving beyond simple "center-point" collision to "closest-edge" or "rect-intersection" for a smoother "snapping" feel.
Design Breakdown
Requirements
Functional Requirements:
Drag items within a list to reorder.
Drag items between different lists.
Visual indicator (placeholder) of where the item will land.
Persistence of the new order.
Non-Functional Requirements:
Performance: 60 FPS during drag operations (avoiding React re-renders for every pixel moved).
Responsiveness: Support for mobile and desktop.
Accessibility: ARIA live regions to announce drag start, movement, and drop results.
Robustness: Graceful handling of "drag cancel" (Esc key or dropping outside targets).
Design Summary
Concise Summary: A coordinate-based system using a "Singular Drag Overlay" for performance and a centralized "Drag Sensor" to manage state.
Major Components:
DND Context Provider: Manages the global state of the active drag operation (ActiveID, OverID, Coordinates).
Draggable Wrapper: A higher-order component or hook that attaches sensors and applies transforms to the "active" element.
Droppable Zone: A component that registers its bounding box with the Context for collision detection.
Drag Overlay: A high-z-index portal that renders the "ghost" element to prevent layout shifts in the source list.
CUJ Walkthrough:
User presses item ->
PointerDown triggers "DragStart" -> Context stores item metadata -> Overlay renders a clone of the item.User moves pointer ->
PointerMove updates coordinates -> Context calculates "Collision" -> "Droppable Zone" shows a placeholder.User releases pointer ->
PointerUp triggers "DragEnd" -> Reorder logic runs -> State updates -> API persists.Simplicity Audit: This architecture uses a single active overlay, avoiding the "jank" caused by moving actual DOM elements within the document flow during the drag.
Architecture Decision Rationale:
Why this?: Using a coordinate-based overlay is the gold standard (similar to
dnd-kit). It keeps the original list intact until the drop, making the math easier and the performance predictable.Requirement Satisfaction: Meets the 60FPS goal by using CSS
transform on a single overlay element and meets accessibility goals by decoupling sensors from the visual representation.System Diagram
Architecture Deep Dive
Presentation Layer
Component Hierarchy: The
DNDContext wraps the entire BoardPage. Columns act as Droppable zones, and Cards act as Draggable elements. A DragOverlay is rendered at the root of the context to provide the visual "ghost" during movement.Interaction Layer: We use Pointer Events to capture both mouse and touch.
touch-action: none is applied to draggables to prevent browser scrolling. ARIA roles (role="button", aria-describedby) are used to ensure screen readers understand the draggable nature.Rendering Layer: To achieve 60FPS, we avoid re-rendering the entire list during movement. Only the
DragOverlay and the Placeholder (via a lightweight state update) are updated. We use transform: translate3d(x, y, 0) for hardware acceleration.UI Frameworks: React for component structure, CSS Modules or Tailwind for styling, and a portal for the
DragOverlay.Application Layer
Data Fetching Layer: Initial board state is fetched via REST. Post-drop, we send a PATCH request with the new
index or order property.State Management Layer: We maintain a
dragState containing activeId, overId, and delta. On DragEnd, we use an "Optimistic Update": the UI updates immediately, and a background request is fired. If the request fails, the OptimisticStore reverts to the previous snapshot.Routing & Navigation: URL tracks the Board ID. Dragging does not affect the route, but "dropping" modifies the underlying resource state.
Domain Layer
Business Rules: Items cannot be moved to "Archived" columns unless they meet specific criteria. Reordering logic uses the
arrayMove pattern (slice and splice) to maintain immutability.Entities / Models:
Board, Column, and Card. Each has a position (float or integer) to determine order.Inter-layer Contracts: The
ReorderService accepts an activeId and an overId and returns a new list of entities, decoupled from React state.Infrastructure Layer
API / Network: Standard REST API. For the MVP, we assume a single-endpoint update
POST /board/:id/reorder.Storage: Use
localStorage as a secondary cache so the user doesn't lose their "drag position" if the tab refreshes mid-operation (though typically, we just rely on the server).Wrap Up
Wrap-up
Evaluation: This design balances performance (Overlay-based) with simplicity (YAGNI).
Trade-offs: We chose a Coordinate-based system over the native HTML5 DnD API because the native API is notoriously difficult to style (the "ghost" image is limited) and has poor touch support.
Optimization: For lists larger than 100 items, we would introduce
windowing (virtualization). For better UX, we could add "Spring physics" for the item snapping back to its position.