How I Built My Own Kanban Board

Raveena PuttaRaveena Putta
5 min read

When I first heard “Kanban board,” I thought of corporate offices with walls covered in sticky notes 🗒️.
Turns out, it’s a super simple but powerful way to organize work: tasks move from To DoIn ProgressDone, giving you a visual picture of progress.

I decided to make my own — not using React, Vue, or any fancy library — but with pure HTML, CSS, and JavaScript.
I wanted to know exactly how each part works, from layout to saving data.


Why I Wanted My Own

My desk looked like a confetti explosion of sticky notes. I had reminders on my phone, notes in three different apps, and ideas written on the back of receipts.
I needed one place to add, edit, move, and save tasks — something that would work in my browser without sign-ups or apps.

So, I rolled up my sleeves, opened up my code editor, and built a Kanban Board using just HTML, CSS, and JavaScript. No heavy frameworks, no magic libraries — just plain old web basics + a bit of logic.


Why a Kanban Board?

I was juggling multiple projects, notes, and ideas. And my desk? A battlefield of scattered sticky notes.
So, I thought:

“Why not make a digital version that looks cleaner, works anywhere, and never runs out of space?”


How I Built It (Step-by-Step)

My Kanban Board Features

  1. Here’s what it can do:
    ✅ Select a priority color for each ticket (pink, green, blue, or black).
    ✅ Create tickets with a + button and delete them with an X button.
    ✅ Each ticket automatically gets a unique ID (no two are ever the same).
    ✅ Change a ticket’s priority color later by clicking the color strip.
    ✅ Lock/unlock a ticket to prevent accidental edits.
    ✅ All tickets are saved in Local Storage, so they’re still there even if I close the tab.


    Step 1: Building the Header & Controls

    At the top, I have:

    • Priority filters (colored boxes) — click one to view only tickets of that color.

    • A + button to open the ticket creation modal.

    • An X button to enter delete mode, so I can click tickets to remove them.

The header’s gradient was pure CSS magic:

    .toolbox-cont {
        background: linear-gradient(to right, #6a11cb, #2575fc);
    }

It’s simple, but it instantly made my app look more polished.


Step 2: Creating Tickets with Unique IDs

When I click +, a modal appears where I can type my task and select its priority color.

Each ticket gets:

  • A color strip showing its priority

  • A unique ID generated with the shortid library

  • The task text

  • A lock icon

Here’s my ticket creation function:

    function createTicket(ticketColor, ticketTask, ticketID) {
        let id = ticketID || shortid(); // Generate if not provided
        let ticketCont = document.createElement("div");
        ticketCont.className = "ticket-cont";
        ticketCont.innerHTML = `
            <div class="ticket-color ${ticketColor}"></div>
            <div class="ticket-id">${id}</div>
            <div class="task-area">${ticketTask}</div>
            <div class="ticket-lock"><i class="fa-solid fa-lock"></i></div>
        `;
        mainCont.appendChild(ticketCont);
        ticketsArr.push({ ticketColor, ticketTask, ticketId: id });
        updateLocalStorage();
    }

That shortid() call makes sure IDs like 7vq4qo4 and e11e3i9 are guaranteed unique.


Step 3: Changing Ticket Priority Color

I didn’t want tickets to be stuck with one priority forever. So I added a click event on the color strip:

    ticketColorDiv.addEventListener("click", () => {
        let currentColorIndex = colors.indexOf(ticketColor);
        let newColorIndex = (currentColorIndex + 1) % colors.length;
        let newColor = colors[newColorIndex];
        ticketColorDiv.classList.replace(ticketColor, newColor);
        ticketColor = newColor;
        updateTicketInStorage(ticketId, ticketColor);
    });

This cycles through my colors array: ["lightpink", "lightgreen", "lightblue", "black"].


Step 4: Locking & Unlocking Tickets

Editing is great — until you accidentally overwrite something.
That’s why each ticket has a lock icon:

  • Locked → You can’t edit text.

  • Unlocked → You can edit text directly in the ticket.

    lockIcon.addEventListener("click", () => {
        if (lockIcon.classList.contains("fa-lock")) {
            lockIcon.classList.replace("fa-lock", "fa-lock-open");
            taskArea.setAttribute("contenteditable", "true");
        } else {
            lockIcon.classList.replace("fa-lock-open", "fa-lock");
            taskArea.setAttribute("contenteditable", "false");
            updateTicketInStorage(ticketId, taskArea.innerText);
        }
    });

Step 5: Deleting Tickets

Clicking the X button on the top bar activates “delete mode.”
In this mode, clicking a ticket removes it from both the UI and Local Storage.

    deleteBtn.addEventListener("click", () => {
        deleteMode = !deleteMode;
    });
    ticketCont.addEventListener("click", () => {
        if (deleteMode) {
            ticketCont.remove();
            ticketsArr = ticketsArr.filter(ticket => ticket.ticketId !== ticketId);
            updateLocalStorage();
        }
    });

Step 6: Saving Everything with Local Storage

Local Storage works like a mini hard drive inside your browser.
It stores key-value pairs permanently until you clear them.
Because it only stores strings, I use JSON.stringify() to save my ticket array and JSON.parse() to read it back:

    function updateLocalStorage() {
        localStorage.setItem("tickets", JSON.stringify(ticketsArr));
    }

    window.addEventListener("load", () => {
        if (localStorage.getItem("tickets")) {
            ticketsArr = JSON.parse(localStorage.getItem("tickets"));
            ticketsArr.forEach(ticket =>
                createTicket(ticket.ticketColor, ticket.ticketTask, ticket.ticketId)
            );
        }
    });

What I Learned

  1. Small touches make a big difference — color cycling and locks make it feel more polished.

  2. One array to rule them allticketsArr is my single source of truth.

  3. Local Storage is the easiest way to add persistence to a browser app.

  4. Keeping UI elements in sync with storage takes discipline but prevents bugs.


Next Steps

I’m planning to add drag-and-drop to move tickets between columns, filtering the tickets according to the colour making it work more like Trello. That means diving into the Drag & Drop API next and some more learning ahead.

Hope you like the article I’ll be improving the UI and add some features to it.Stay tuned for my upcoming articles.

0
Subscribe to my newsletter

Read articles from Raveena Putta directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Raveena Putta
Raveena Putta