ui-053

Kanban Board

£5

A kanban board with drag and drop: cards lift and tilt as you drag between columns, other cards Flip aside and column counts roll.

Flip advanced
6 more details
flip-layoutdragkeyboard-reordertouch-dragvelocity-reactivemicro-interaction
Kanban Board - UI component preview

This element reads better at your own screen size than in the frame below:

About this component

A kanban board where the space a card will land in is always visible. Pick up a card and it lifts: a small scale, a longer shadow, a 1px accent ring and a tilt that leans with the pointer's horizontal speed and settles back as the pointer slows. A slot the height of the card opens under it, in its own column or any other, and every card in the way Flips aside to make room. Let go and the card settles into the slot on a short damped ease. The column counts roll up and down as the slot crosses between columns, a card dropped into Done has its title struck through with a quick draw, and dragging a card out of Done takes the line away again. An emptied column shows a drop zone that takes a card like any other.

Read the full effect overview

The keyboard can do everything the pointer can. Focus a card, press Space to pick it up, move it with the arrow keys within a column and across columns, then Space to drop or Escape to put it back where it started. Each step is announced through a live region: "Moved 'Audit the onboarding email sequence' to In progress, position 2 of 4." On a phone a long press of about 200ms picks a card up, so an ordinary swipe still scrolls the page; the columns become a horizontal snap scroller, one column per view, and holding a card near the edge pages to the next column.

Every committed move dispatches a bubbling kanban:move event with the card id, the source and target columns and the new index, so saving the board is one listener. One HTML file, one stylesheet, one script: vanilla JavaScript with GSAP and the Flip plugin, no framework. Light and dark themes come from one set of custom properties switched by a data-variant attribute.

What's included

12 items
  • Cards drag across and within columns, lifted with a scale, a shadow and a 1px accent ring
  • A tilt driven by the pointer's horizontal velocity, damped twice so it leans and recovers rather than wobbles
  • A slot the height of the card opens where it will land, and every other card Flips aside, across columns too
  • A settle on drop from wherever the card is drawn into the slot, with no transform left on any card
  • Column counts that roll up or down as they change, including while the slot is crossing columns
  • Dropping into the done column draws a strike through the title; dragging out removes it
  • An empty column shows a drop zone that accepts a card
  • Full keyboard moves: Space picks up, arrows move within and across columns, Space drops, Escape cancels to the origin
  • A polite live region announcing every pick-up, move, drop and cancel by column and position
  • Touch-aware: a 200ms long press picks up, so swipes still scroll; a snap scroller of one column per view on a phone, with edge paging while dragging
  • A bubbling kanban:move event carrying id, from, to and index
  • Light and dark themes on one set of custom properties

Perfect for

5 use cases
  • Task and issue boards in a project management or internal tool
  • Sales pipelines and hiring funnels where each card is a deal or a candidate
  • Content calendars and editorial workflows moving pieces from draft to published
  • Support queues and triage boards in an admin console
  • Onboarding checklists a customer moves through by stage

How it works

4 sections

Picking up, and the slot that follows

When a press becomes a drag (4px of net travel with a mouse, a 200ms hold with a finger) the card's place in its list is taken by a slot of the same height, and the card itself is lifted out, fixed to the viewport where it was, so it can travel over every column and past the edge of a scrolling container. On every move the component works out which column is under the pointer and how many resting cards sit above the card's middle; resting positions come from offsetTop, so the transforms of cards still in flight never make the answer flicker.

Flip for everything that makes room

Moving the slot is one DOM insertion wrapped in Flip.getState and Flip.from, so every card that has to make room, in the same column and in the column the slot just left, glides from its old position to its new one. Interrupting a Flip mid-flight with another move is safe: the next state is captured from where the cards are drawn, not where they were headed.

The tilt, the settle and the strike

A ticker smooths the pointer's horizontal speed and then eases the rotation toward it, so a quick flick leans the card a few degrees and a pause brings it upright. On release the card goes back into the list where the slot was and is tweened from its drawn position, rotation and scale back to rest. The done strike is a background line on the inline title, so it follows every line of a wrapped title, drawn by tweening one custom property from 0% to 100%.

Keyboard, touch and reduced motion

Keyboard moves use the same Flip, with focus kept on the card through each DOM move and a blur the component did not cause dropping the card rather than stranding it. On touch the drag only starts after a still long press, and from then the touchmove is cancelled so the page stays put. Under reduced motion every move happens instantly with no tilt, which keeps the board fully usable.

Plugins Flip
Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this UI element's demo, 15 September 2026. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid UI element

Purchase to unlock the code.

Buying Kanban Board opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£5 Standard license, unlimited projects

Browse UI elements

Documentation

What's Included

  • index.html: the demo page and the markup to copy

  • assets/style.css: component styles, plain CSS custom properties for colours

  • assets/script.js: readable, commented source with an onReady guard

  • Cards drag across and within columns, lifted with a small scale, a longer shadow and a 1px accent ring

  • A tilt driven by the pointer's horizontal velocity, smoothed and then eased, so a flick leans the card a few degrees and a pause brings it upright

  • A slot the height of the card opens where it will land, and every card in the way glides aside with GSAP Flip, in the same column and across columns

  • A settle on drop from wherever the card is drawn into the slot, after which no card carries a leftover transform

  • Column counts that roll up or down when they change, including while the slot crosses between columns

  • A strike drawn through the title when a card lands in the done column, removed again when it leaves

  • An empty-state drop zone in any column that has no cards

  • A full keyboard model with a polite live region, and a long press on touch so ordinary swipes still scroll

  • A bubbling kanban:move event carrying the card id, the two columns and the new index

  • Roles used: <section> columns labelled by their <h2>, <ul> / <li> card lists, focusable cards described by the instruction line, and a role="status" aria-live="polite" region

Quick Start

1. Add to your <head>:

Code snippet omitted: it ships with the download.

2. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

3. Add the board markup (one column shown; copy the whole board from index.html):

Code snippet omitted: it ships with the download.

The script only looks for the data- attributes, so the classes are yours to rename (the one exception is kb-count-num, the class it gives the digits it creates, and kb-slot, the class of the landing slot):

Attribute On What it does
data-kanban the board root Every board on the page with this attribute is wired up on its own
data-kanban-scroller the element holding the columns Optional. When it scrolls sideways (the phone layout), holding a dragged card near its edge pages to the next column
data-kanban-col="id" a column The value is what kanban:move reports as from and to
data-kanban-done the done column Cards dropped here are struck through; cards dragged out lose the line
data-kanban-col-title the column heading The column name used in announcements
data-kanban-count the count badge Rewritten and rolled by the script whenever the column's count changes
data-kanban-list the card container Cards must be its direct children. Keep it position: relative: card positions are measured against it
data-kanban-card a card Needs tabindex="0"; data-id is the id reported in the event
data-kanban-title an inline element in a card The title, used in announcements and carrying the strike
data-kanban-empty an element in a column The empty-state drop zone, shown only while the column holds no cards
data-kanban-live any element in the board The live region. Optional; leave it out and the board is silent

Two pieces of CSS the component depends on if you restyle from scratch: no card may carry a CSS transform (GSAP reads one as pixels and a tween back to rest never clears it), and .kb-card.is-dragging must stay position: fixed; pointer-events: none, because the card travels over the viewport while it is held.

The theme switcher, the .showcase-toolbar and the .stage wrapper in index.html are demo furniture, not part of the component; leave them behind.

Saving a move

Code snippet omitted: it ships with the download.

A drop back in the slot it came from, or a cancel, changes nothing and fires nothing.

Keyboard & Accessibility

Key What it does
Tab Moves through the cards
ArrowUp / ArrowDown Moves focus to the card above or below; while a card is picked up, moves the card itself
ArrowLeft / ArrowRight Moves focus to the nearest card in the neighbouring column; while picked up, moves the card into that column at the same position
Home / End While picked up, sends the card to the top or bottom of its column
Space / Enter Picks up the focused card, or drops the one that is picked up
Escape Cancels, returning the card to the column and position it was picked up from. Also cancels a pointer drag in progress
  • Announcements go to the role="status" aria-live="polite" region: "Picked up 'Draft the Q4 pricing page copy', To do, position 1 of 4. Arrow keys move it, Space drops it, Escape cancels.", then "Moved 'Draft the Q4 pricing page copy' to In progress, position 2 of 4.", then a drop or "Move cancelled. 'Draft the Q4 pricing page copy' is back in To do, position 1 of 4."
  • Each card points at the instruction line with aria-describedby, and columns are <section>s labelled by their headings, so a screen reader hears "In progress, list, 3 items".
  • Moving a card in the DOM blurs it, so the component puts focus straight back and ignores that one blur. A blur it did not cause drops the card where it is rather than stranding it picked up.
  • Touch: a still press of about 200ms picks a card up. A finger that moves before then is a scroll and the page gets it, so the board never traps vertical scrolling on a phone. After the pick-up the component cancels touchmove, so the page holds still under the drag, and the long-press callout and context menu are suppressed.
  • prefers-reduced-motion: the same moves with every duration at zero and no tilt. Cards change places instantly; nothing is disabled.
  • Without JavaScript the board is a readable set of lists, with the done column already struck through.

How It Works

Picking up. A mouse press becomes a drag after 4px of net travel from the press point (net, not accumulated, so a wobbly click stays a click); a finger needs a 200ms hold. The card's place is taken by a slot of the same height and the card is lifted out to the board root, fixed to the viewport where it was, so it can cross every column and the edge of a scrolling container. pointermove and pointerup are bound to window for the life of the gesture rather than captured on the card, because a pointer capture is dropped the moment its element moves in the DOM.

The slot, and Flip. On every move the component picks the column under the pointer (or the nearest visible one) and counts the resting cards whose middle is above the dragged card's middle. Resting positions come from offsetTop, which ignores transforms, with the slot's height taken back out, so cards still gliding never make the target flicker. When the target changes, the slot moves with one insertBefore wrapped in Flip.getState() and Flip.from(), which is what glides every card that has to make room, in both columns.

The tilt and the settle. A gsap.ticker callback smooths the pointer's horizontal speed and eases the rotation toward it, capped at 7 degrees, which is why the lean is felt rather than seen as wobble. On release the card replaces the slot and is tweened with gsap.fromTo from its drawn position, rotation and scale back to rest, then its transform is cleared outright.

Counts and the strike. A count badge stacks the old and new digits in one grid cell and rolls them past each other with yPercent, up when the number rises and down when it falls. The strike is a background line on the inline title, so it follows every line of a wrapped title; the script tweens its --strike custom property from 0% to 100% to draw it.

Phones. Under 640px the columns become a horizontal scroll-snap scroller, one column per view with a sliver of the next. Holding a dragged card within 44px of the scroller's edge for a quarter of a second pages to the next column, and the target is recomputed as it scrolls.

Customisation

  • --accent is the focus ring, the slot outline and the lifted card's ring; --accent-wash is the slot's fill.
  • The timings and the gesture tuning (LIFT_SCALE, FLIP_TIME, SETTLE_TIME, TOUCH_HOLD, TILT_MAX, EDGE and friends) are named constants at the top of assets/script.js.
  • Add or remove columns freely: the board reads whatever [data-kanban-col] elements it contains. Change grid-template-columns on .kb-cols to match.
  • To render cards from your own data, give each new card the same markup and attributes; the board reads the DOM on every gesture, so nothing needs re-initialising.

Themes

The board ships in two themes, light (the default) and dark, which are one design at two token values. Set the attribute on <body>, or on the board's own wrapper in your app:

Code snippet omitted: it ships with the download.

Every colour, radius and shadow the board uses is a custom property in the matching body[data-variant="..."] block in assets/style.css: --ground, --ground-2, --raised, --hover, --ink, --ink-2, --ink-3, --line, --line-strong, --avatar, --accent, --accent-ink, --accent-wash, --shadow, --card-shadow, --lift-shadow, --radius, --radius-sm, --radius-xs and --font. Nothing in assets/script.js reads the theme name, so re-valuing one block is the whole of a rebrand: point --accent at your brand colour and the surfaces and --ink-* ramp at your own. The demo's ?variant=dark URL parameter only sets the same attribute.

Requirements

  • GSAP 3.12+ with the Flip plugin
  • No build step, no framework

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

The Vault library, plus future additions to the library.