webdevpuneet.com

Candidate Pipeline Kanban — Free Recruiting Board With Real Drag & Drop

Candidate Pipeline Kanban · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="cpk-board" id="cpkBoard">
  <section class="cpk-col" data-stage="applied">
    <header class="cpk-col-head"><h3>Applied</h3><span class="cpk-count" id="cpkCount-applied">3</span></header>
    <div class="cpk-dropzone" data-stage="applied">
      <article class="cpk-card" draggable="true" data-id="c1">
        <div class="cpk-card-top"><span class="cpk-avatar" style="background:#6d5efc">MK</span><strong>Maya Kessler</strong></div>
        <p class="cpk-role">Senior Product Designer</p>
        <div class="cpk-tags"><span class="cpk-tag">Figma</span><span class="cpk-tag">Design Systems</span></div>
      </article>
      <article class="cpk-card" draggable="true" data-id="c2">
        <div class="cpk-card-top"><span class="cpk-avatar" style="background:#22d3ee">RT</span><strong>Ravi Thakur</strong></div>
        <p class="cpk-role">Backend Engineer</p>
        <div class="cpk-tags"><span class="cpk-tag">Go</span><span class="cpk-tag">Postgres</span></div>
      </article>
      <article class="cpk-card" draggable="true" data-id="c3">
        <div class="cpk-card-top"><span class="cpk-avatar" style="background:#f472b6">SL</span><strong>Sara Lindqvist</strong></div>
        <p class="cpk-role">Growth Marketer</p>
        <div class="cpk-tags"><span class="cpk-tag">SEO</span><span class="cpk-tag">Paid Media</span></div>
      </article>
    </div>
  </section>

  <section class="cpk-col" data-stage="screening">
    <header class="cpk-col-head"><h3>Screening</h3><span class="cpk-count" id="cpkCount-screening">2</span></header>
    <div class="cpk-dropzone" data-stage="screening">
      <article class="cpk-card" draggable="true" data-id="c4">
        <div class="cpk-card-top"><span class="cpk-avatar" style="background:#34d399">JB</span><strong>Jordan Blake</strong></div>
        <p class="cpk-role">Data Analyst</p>
        <div class="cpk-tags"><span class="cpk-tag">SQL</span><span class="cpk-tag">Python</span></div>
      </article>
      <article class="cpk-card" draggable="true" data-id="c5">
        <div class="cpk-card-top"><span class="cpk-avatar" style="background:#fbbf24">EP</span><strong>Elena Popescu</strong></div>
        <p class="cpk-role">Customer Success Lead</p>
        <div class="cpk-tags"><span class="cpk-tag">SaaS</span><span class="cpk-tag">Onboarding</span></div>
      </article>
    </div>
  </section>

  <section class="cpk-col" data-stage="interview">
    <header class="cpk-col-head"><h3>Interview</h3><span class="cpk-count" id="cpkCount-interview">1</span></header>
    <div class="cpk-dropzone" data-stage="interview">
      <article class="cpk-card" draggable="true" data-id="c6">
        <div class="cpk-card-top"><span class="cpk-avatar" style="background:#f87171">DN</span><strong>Diego Nunez</strong></div>
        <p class="cpk-role">Frontend Engineer</p>
        <div class="cpk-tags"><span class="cpk-tag">React</span><span class="cpk-tag">TypeScript</span></div>
      </article>
    </div>
  </section>

  <section class="cpk-col" data-stage="offer">
    <header class="cpk-col-head"><h3>Offer</h3><span class="cpk-count" id="cpkCount-offer">0</span></header>
    <div class="cpk-dropzone" data-stage="offer"></div>
  </section>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Native HTML5 drag-and-drop
draggable, dragstart, dragover, drop — no library.
Real DOM relocation
insertBefore/appendChild move the actual node.
Position-aware reordering
getDragAfterElement uses pointer Y vs card midpoints.
Drag visual feedback
Dragging card fades; target column outlines.
Live count badges
Recompute from actual DOM children after each drop.
Four-stage pipeline
Applied, Screening, Interview, Offer out of the box.
Candidate tags
Skill chips per card for quick scanning.
Zero dependencies
Native browser APIs only.

About this UI Snippet

Candidate Pipeline Kanban — Draggable Recruiting Stages

Screenshot of the Candidate Pipeline Kanban snippet rendered live

The candidate pipeline kanban is the recruiter's home screen: candidates as cards moving through Applied, Screening, Interview, and Offer columns. This snippet implements genuinely working drag-and-drop using the native HTML5 Drag and Drop API — no library, and no click-to-move fallback standing in for real dragging.

Real HTML5 drag events, not a simulation

Every card has draggable="true". dragstart records the dragged element and fades it with cpk-dragging; dragend cleans that state up. Each column's .cpk-dropzone listens for dragover (calling e.preventDefault(), which is what tells the browser this element is a valid drop target at all) and drop. Without that preventDefault call, the browser's default is to reject the drop entirely — it's the one line that makes the whole interaction possible.

Cards actually move in the DOM

On dragover, getDragAfterElement compares the pointer's Y position against the vertical midpoint of every other card in that column to find where the dragged card should land, then insertBefore/appendChild physically relocates the dragged element — the same node, not a clone — into its new position. That's what makes this real drag-and-drop: the element you picked up is the element that lands, mid-column reordering included, not just a column swap.

Visual feedback during the drag

The dragged card gets partial opacity via cpk-dragging, and the column currently under the pointer gets a dashed outline via cpk-dragover — both cleared on dragend/dragleave so the board never gets stuck in a mid-drag visual state.

Counts stay in sync

updateCounts() recounts each column's .cpk-card children and updates its badge after every drop, so the header numbers never drift from what's actually rendered.

Customizing it

Wire the drop handler to persist the new stage to your backend, add a confirmation before moving a candidate to Offer, or add a WIP limit per column. Pair it with a kanban board for a general task-board version, a drag-sort-list for single-column reordering, or a job listing card for the postings these candidates applied to.

Step by step

How to Use

  1. 1
    Paste the HTML, CSS, and JSFour columns of candidate cards render.
  2. 2
    Press and drag a cardIt fades slightly and follows your cursor.
  3. 3
    Drag over another columnThat column outlines with a dashed border.
  4. 4
    Drop the cardIt actually relocates into the DOM at that position.
  5. 5
    Drag within the same columnCards reorder based on where you drop.
  6. 6
    Watch the column countsBadges update to match the real card counts.

Real-world uses

Common Use Cases

Recruiting dashboards
Pair with a job listing card to build a recruiter home screen, with candidates dragged between Applied, Screening, Interview and Offer.
ATS internal tools
Model hiring stages as columns exactly like a real applicant tracking system, using native HTML5 drag and drop with no library.
General task boards
See the broader kanban board pattern, and reuse this one's getDragAfterElement for position-aware reordering inside a column.
Sales pipelines
Swap candidates for deals across stages. The dragged card fades and the target column outlines, giving clear drag feedback.
Onboarding and RSVP triage
Move new hires or event attendees through review stages, and combine with an onboarding checklist widget for the next step.
Related: Content Calendar Grid
See the Content Calendar Grid for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It is real HTML5 drag-and-drop. Cards carry draggable="true" and the script listens for the actual dragstart, dragover, and drop events the browser fires during a native drag gesture. The dragover handler calls e.preventDefault(), which is required by the spec to mark an element as a valid drop target — without it the browser's default behavior rejects the drop.

getDragAfterElement compares the current pointer Y position against the vertical midpoint of every other card in that column (excluding the one being dragged) using getBoundingClientRect, and finds the closest card whose midpoint is still below the pointer. The dragged element is then inserted before that card with insertBefore, or appended to the end if none qualifies — so mid-column reordering works, not just column-to-column moves.

The same DOM node moves. draggedCard holds a reference to the actual element clicked in dragstart, and dragover repeatedly calls insertBefore or appendChild on that exact reference as the pointer moves — so by the time drop fires, the card has already been physically relocated in the DOM tree, not swapped for a clone.

In the drop handler (after updateCounts() runs), read the dragged card's data-id and the dropzone's data-stage attribute, then send that pair to your API. Since the DOM move already happened during dragover, the drop handler is purely the moment to fire the network request — you don't need to move anything there yourself.

Native HTML5 drag-and-drop events work the same way inside any framework component — bind dragstart/dragover/drop with your framework's event syntax and keep a ref or local variable for the dragged item's id. On drop, update your state's stage field for that candidate rather than moving DOM nodes directly, and let the framework re-render the columns from state. The getDragAfterElement positioning logic ports as-is since it only reads layout geometry.