One of the more interesting features of this portfolio is the kanban board system. Each project has its own board with columns and draggable cards, visible to anyone visiting the site. Here's how it works under the hood.
Why a Kanban Board?
Most portfolios show finished work. I wanted mine to show work in progress too — not just what I've built, but what I'm actively building. A kanban board makes that transparent in a way that's immediately readable to anyone who visits.
The Data Model
The board system is built on four database models:
- Project — the top level entity, owns a board
- Board — belongs to a project, contains columns
- BoardColumn — belongs to a board, contains cards, has a position
- Card — belongs to a column, has a title, description, priority, and position
Each project gets a board automatically when it's created, with four default columns — Backlog, In Progress, Testing, and Done.
In SQLAlchemy the relationships look roughly like this:
class Project(db.Model):
board = db.relationship("Board", backref="project", uselist=False)
class Board(db.Model):
columns = db.relationship("BoardColumn", order_by="BoardColumn.position")
class BoardColumn(db.Model):
cards = db.relationship("Card", order_by="Card.position")
The cascade delete ensures that when a project is deleted, its board, columns, and cards are all cleaned up automatically.
The Admin Board View
The admin board renders all columns side by side with their cards. Each card shows its title, priority level, and a delete button. New cards can be added to any column via a small form at the bottom of each column.
Priority is indicated visually through a colored left border on each card:
- Green — low priority
- Yellow — medium priority
- Red — high priority
Adding Drag and Drop with SortableJS
The drag and drop functionality is handled by SortableJS — a lightweight JavaScript library that makes any list sortable with minimal setup.
Each column is initialized as a Sortable instance with a shared group name, which allows cards to be dragged between columns:
Sortable.create(column, {
group: "board",
animation: 150,
ghostClass: "card-ghost",
dragClass: "card-dragging",
onEnd: function(evt) {
const cardId = evt.item.dataset.cardId;
const columnId = evt.to.dataset.columnId;
fetch(`/admin/board/move_card/${cardId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ column_id: columnId })
});
}
});
When a card is dropped into a new column the onEnd callback fires,
sending a small JSON request to Flask to update the card's column in
the database. No page reload needed.
The Drag Effect
The visual effect when dragging a card was important to get right. The goal was for the card to feel like it was physically lifting off the board. This is achieved with CSS classes that SortableJS applies automatically during a drag:
.card-dragging {
transform: rotate(2deg) scale(1.05);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6), 0 0 20px var(--glow);
z-index: 999;
}
.card-ghost {
opacity: 0.3;
border: 1px dashed var(--accent);
background: var(--accent-dim);
}
The dragged card rotates slightly and scales up with a strong drop shadow, while a ghost placeholder stays in the original position so you can see where the card came from.
The Public Read-Only View
Visitors to the site see a read-only version of the board on each project page. It renders the same columns and cards but without the add, delete, or drag functionality. The data is the same — it's just rendered without the interactive elements.
This means anyone visiting kylewheatley.com can see exactly what I'm working on at any given moment, updated in real time as I move cards around in the admin panel.
The Overall Board
There's also an overall board on the projects page that shows all projects grouped by status — Active, On Hold, and Completed. Unlike the per-project boards this one is auto-generated from project statuses rather than manually managed. When I change a project's status in the admin panel it automatically moves to the correct column on the overall board.
What I'd Add Next
A few things I'd like to improve on the board system in the future:
- Card descriptions with a markdown editor
- Due dates on cards
- Drag to reorder cards within a column
- Card labels or tags beyond just priority
For now it does exactly what I need it to — gives anyone visiting the site a live window into what I'm working on.