
Trello Was Too Much
I kept opening Trello to add a task and spending ten minutes instead updating labels, checking power-ups, and moving things between boards that had multiplied past the point of usefulness. I just wanted somewhere to put tasks and drag them to done. That's it.
KanBuddy has no account. No sync. No notifications. It stores everything in localStorage and that's the whole system. I based it loosely on Basecamp's idea of 'calm software' — tools that don't demand your attention, they just wait for it.
localStorage and Nothing Else
The whole board serializes to a single JSON blob in localStorage. No backend, no auth, no API key. Open the tab and your board is there. Close it and nothing was sent anywhere. That simplicity shaped every other decision.
Drag-and-drop had to feel right from day one, not be added later. DnD Kit over react-beautiful-dnd because it lets you write your own collision detection — which matters a lot when cards are short and the list is dense and you need precise control over where something drops.
Board Layout
Started with the minimum: columns and cards. Mapped out every interaction before writing any code — what happens when you click a card title, where the drag handle appears, how a new column gets added. Nothing clever, just making sure the basics were right.
Horizontally scrollable board, fixed-width columns, fluid height based on card count
Editable title, task count, add-card button
Click to open inline editor, drag handle appears on hover
DnD Kit with a custom overlay that shows a drop shadow during drag
Zustand's persist middleware serializes the full board to localStorage on every state change
Under the Hood
Flat state store with immer for immutable updates. Persist middleware handles the localStorage serialization without any extra wiring.
Custom closest-center collision detection for dense card lists. Keyboard accessible — tab to select, space to lift, arrow keys to move, space to drop.
Scoped per component. Theme tokens as CSS custom properties — no runtime CSS-in-JS, no class name collisions.
Every board mutation writes to localStorage synchronously. No debounce, no batching — state is always current.
What Made It Hard
- DnD Kit's default collision detection places drops too loosely when cards are short. Wrote a custom closest-center strategy and added a visible insertion line to show exactly where the card will land before you release.
- Keyboard drag-and-drop needs screen reader announcements at each step — card lifted, moved to column X at position Y, dropped or cancelled. That required ARIA live regions updating on every drag event.
- localStorage caps at 5MB. With enough cards and columns that's reachable. Added a size check on every write that warns before the board hits the limit, rather than silently failing when it does.