Skip to content
Product block

Kanban Board Columns

Four columns of prioritised, tagged, assigned task cards with a blocked state — the board as a layout contract, leaving drag behaviour to your own state and library.

203 lineslucide-reactAdded 13 Aug 2026Updated 10 Sept 2026
  • kanban
  • board
  • tasks
  • project
  • columns

What's included

  • components/kanban-board.tsx
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add kanban-board

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Start a page with this section — add more, order them, and leave with the page source.

Preview

Backlog

3
  • HighHL-341

    Rate-limit the export endpoint

    apiinfra
    ACAlex Chen
  • MediumHL-338

    Empty-state illustrations for search

    design
    DWDana Whitfield
  • LowHL-329

    Migrate billing webhooks to v2

    billing
    RMRosa Martínez

In progress

3
  • HighHL-322

    Checkout: support SEPA direct debit

    payments
    PRPriya Raman#482
  • MediumHL-318

    Rotate staging TLS certificates

    Blocked — Waiting on DNS delegation

    infra
    SOSam Okafor
  • MediumHL-325

    New onboarding checklist

    growth
    JWJonas Weber

In review

2
  • HighHL-314

    Fix duplicate invoice emails

    billing
    RMRosa Martínez#479
  • LowHL-311

    Dashboard loading skeletons

    design
    DWDana Whitfield#476

Done

2
  • MediumHL-306

    Upgrade CI runners to Node 22

    infra
    ACAlex Chen
  • HighHL-302

    Search relevance tuning

    search
    PRPriya Raman

Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.

Source

components/kanban-board.tsx
/**
 * <KanbanBoard> — a four-column project board.
 *
 * This is the layout contract, not the interaction. Dragging is behavior
 * you wire to your own state and whichever dnd library you already use —
 * shipping one here would mean shipping a store too, and ripping a store
 * out is harder than adding one. What the markup does settle: column
 * width, horizontal overflow on small screens, and everything a card must
 * carry — priority, tags, owner, date or PR — without growing past a
 * glance, because a card you have to read line by line defeats the board.
 *
 * "Blocked" is a tint plus an icon and a reason, never colour alone.
 */

import * as React from 'react'
import { Plus, GitPullRequest, OctagonAlert, CalendarDays } from 'lucide-react'

export type KanbanPriority = 'high' | 'medium' | 'low'

export interface KanbanTask {
  id: string
  title: string
  priority: KanbanPriority
  tags: string[]
  assignee: { name: string; initials: string }
  /** ISO date — rendered as a short due date. */
  due?: string
  /** Pull request number — rendered as a PR link instead of a date. */
  pr?: number
  /** Short reason the task is stuck, e.g. "Waiting on DNS". */
  blocked?: string
}

export interface KanbanColumn {
  id: string
  label: string
  tasks: KanbanTask[]
}

export interface KanbanBoardProps {
  columns?: KanbanColumn[]
  className?: string
}

const PRIORITY: Record<KanbanPriority, { label: string; dot: string }> = {
  high: { label: 'High', dot: 'bg-rose-500' },
  medium: { label: 'Medium', dot: 'bg-amber-500' },
  low: { label: 'Low', dot: 'bg-sky-500' },
}

const ALEX = { name: 'Alex Chen', initials: 'AC' }
const DANA = { name: 'Dana Whitfield', initials: 'DW' }
const ROSA = { name: 'Rosa Martínez', initials: 'RM' }
const PRIYA = { name: 'Priya Raman', initials: 'PR' }
const SAM = { name: 'Sam Okafor', initials: 'SO' }
const JONAS = { name: 'Jonas Weber', initials: 'JW' }

const DEFAULT_COLUMNS: KanbanColumn[] = [
  {
    id: 'backlog',
    label: 'Backlog',
    tasks: [
      { id: 'HL-341', title: 'Rate-limit the export endpoint', priority: 'high', tags: ['api', 'infra'], assignee: ALEX, due: '2026-08-19' },
      { id: 'HL-338', title: 'Empty-state illustrations for search', priority: 'medium', tags: ['design'], assignee: DANA, due: '2026-08-21' },
      { id: 'HL-329', title: 'Migrate billing webhooks to v2', priority: 'low', tags: ['billing'], assignee: ROSA, due: '2026-08-25' },
    ],
  },
  {
    id: 'in-progress',
    label: 'In progress',
    tasks: [
      { id: 'HL-322', title: 'Checkout: support SEPA direct debit', priority: 'high', tags: ['payments'], assignee: PRIYA, pr: 482 },
      { id: 'HL-318', title: 'Rotate staging TLS certificates', priority: 'medium', tags: ['infra'], assignee: SAM, due: '2026-08-14', blocked: 'Waiting on DNS delegation' },
      { id: 'HL-325', title: 'New onboarding checklist', priority: 'medium', tags: ['growth'], assignee: JONAS, due: '2026-08-15' },
    ],
  },
  {
    id: 'in-review',
    label: 'In review',
    tasks: [
      { id: 'HL-314', title: 'Fix duplicate invoice emails', priority: 'high', tags: ['billing'], assignee: ROSA, pr: 479 },
      { id: 'HL-311', title: 'Dashboard loading skeletons', priority: 'low', tags: ['design'], assignee: DANA, pr: 476 },
    ],
  },
  {
    id: 'done',
    label: 'Done',
    tasks: [
      { id: 'HL-306', title: 'Upgrade CI runners to Node 22', priority: 'medium', tags: ['infra'], assignee: ALEX, due: '2026-08-11' },
      { id: 'HL-302', title: 'Search relevance tuning', priority: 'high', tags: ['search'], assignee: PRIYA, due: '2026-08-12' },
    ],
  },
]

const DATE_FORMAT = new Intl.DateTimeFormat('en', {
  month: 'short',
  day: 'numeric',
  timeZone: 'UTC',
})

export function KanbanBoard({ columns = DEFAULT_COLUMNS, className = '' }: KanbanBoardProps) {
  // `relative` on the rail is load-bearing. Tailwind's `sr-only` is
  // `position: absolute`, and an overflow box only clips an absolutely
  // positioned descendant when it is also that descendant's containing block.
  // Without it the assignee names in the right-hand columns laid out at their
  // static position — 959px in on a 390px screen — escaped this scroller and
  // gave the whole PAGE a sideways scroll, pointing at nothing anyone could
  // see. Same bug, same fix as the table on /compare.
  return (
    <div className={`relative overflow-x-auto ${className}`}>
      <div className="flex min-w-max items-start gap-4 p-1">
        {columns.map((column) => (
          <section key={column.id} aria-label={column.label} className="w-72 shrink-0">
            <div className="mb-3 flex items-center gap-2 px-1">
              <h2 className="text-sm font-semibold tracking-tight">{column.label}</h2>
              <span className="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
                {column.tasks.length}
              </span>
              <button
                type="button"
                aria-label={`Add task to ${column.label}`}
                className="ms-auto inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
              >
                <Plus aria-hidden className="h-4 w-4" />
              </button>
            </div>

            <ul className="space-y-2.5">
              {column.tasks.map((task) => {
                const priority = PRIORITY[task.priority]
                return (
                  <li
                    key={task.id}
                    className={`rounded-xl border p-3 transition-colors ${
                      task.blocked
                        ? 'border-destructive/40 bg-destructive/5'
                        : 'border-border/60 bg-card hover:border-border'
                    }`}
                  >
                    <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
                      <span aria-hidden className={`h-2 w-2 rounded-full border border-transparent ${priority.dot}`} />
                      <span>{priority.label}</span>
                      <span className="ms-auto font-mono text-[0.65rem]">{task.id}</span>
                    </div>

                    <p className="mt-1.5 text-sm font-medium leading-snug">{task.title}</p>

                    {task.blocked ? (
                      <p className="mt-1.5 flex items-center gap-1.5 text-xs font-medium text-destructive">
                        <OctagonAlert aria-hidden className="h-3.5 w-3.5" />
                        Blocked — {task.blocked}
                      </p>
                    ) : null}

                    <div className="mt-2 flex flex-wrap gap-1">
                      {task.tags.map((tag) => (
                        <span
                          key={tag}
                          className="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground"
                        >
                          {tag}
                        </span>
                      ))}
                    </div>

                    <div className="mt-2.5 flex items-center justify-between">
                      <span
                        title={task.assignee.name}
                        className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground"
                      >
                        {task.assignee.initials}
                        <span className="sr-only">{task.assignee.name}</span>
                      </span>

                      {task.pr ? (
                        <a
                          href={`https://github.com/acme/hoverlab/pull/${task.pr}`}
                          className="inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
                        >
                          <GitPullRequest aria-hidden className="h-3.5 w-3.5" />
                          #{task.pr}
                        </a>
                      ) : task.due ? (
                        <time
                          dateTime={task.due}
                          className="inline-flex items-center gap-1 text-xs text-muted-foreground"
                        >
                          <CalendarDays aria-hidden className="h-3.5 w-3.5" />
                          {DATE_FORMAT.format(new Date(task.due))}
                        </time>
                      ) : null}
                    </div>
                  </li>
                )
              })}
            </ul>
          </section>
        ))}
      </div>
    </div>
  )
}

Before you paste

  • Styling is Tailwind utility classes on semantic tokens (bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.
  • Install: npm i lucide-react
  • Every prop has a default, so it renders standalone before you wire it up.

Where it goes

Drop it at components/kanban-board.tsx and import it where you need the section:

import { KanbanBoard } from '@/components/kanban-board'

Props

Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.

PropTypeDefault
columnsKanbanColumn[]DEFAULT_COLUMNS, className = ''
classNamestring—

Not using React?

The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.

kanban-board.html
<!--
  Kanban Board Columns — markup from the Hoverlab catalog.

  This is the block rendered once to HTML and wrapped as a component
  file. It is not a port of the React source: the Tailwind classes carry
  the design, which is the part that took the work, and they are the same
  in every framework.

  This block has no interactive behaviour, so nothing is missing.
-->
<div class="relative overflow-x-auto ">
  <div class="flex min-w-max items-start gap-4 p-1">
    <section aria-label="Backlog" class="w-72 shrink-0">
      <div class="mb-3 flex items-center gap-2 px-1">
        <h2 class="text-sm font-semibold tracking-tight">Backlog</h2>
        <span class="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">3</span>
        <button type="button" aria-label="Add task to Backlog" class="ms-auto inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus h-4 w-4" aria-hidden="true">
            <path d="M5 12h14"></path>
            <path d="M12 5v14"></path>
          </svg>
        </button>
      </div>
      <ul class="space-y-2.5">
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-rose-500"></span>
            <span>High</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-341</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Rate-limit the export endpoint</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">api</span>
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">infra</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Alex Chen" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">AC<span class="sr-only">Alex Chen</span></span>
            <time dateTime="2026-08-19" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 19</time>
          </div>
        </li>
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-amber-500"></span>
            <span>Medium</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-338</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Empty-state illustrations for search</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">design</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Dana Whitfield" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">DW<span class="sr-only">Dana Whitfield</span></span>
            <time dateTime="2026-08-21" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 21</time>
          </div>
        </li>
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-sky-500"></span>
            <span>Low</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-329</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Migrate billing webhooks to v2</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">billing</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Rosa Martínez" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">RM<span class="sr-only">Rosa Martínez</span></span>
            <time dateTime="2026-08-25" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 25</time>
          </div>
        </li>
      </ul>
    </section>
    <section aria-label="In progress" class="w-72 shrink-0">
      <div class="mb-3 flex items-center gap-2 px-1">
        <h2 class="text-sm font-semibold tracking-tight">In progress</h2>
        <span class="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">3</span>
        <button type="button" aria-label="Add task to In progress" class="ms-auto inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus h-4 w-4" aria-hidden="true">
            <path d="M5 12h14"></path>
            <path d="M12 5v14"></path>
          </svg>
        </button>
      </div>
      <ul class="space-y-2.5">
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-rose-500"></span>
            <span>High</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-322</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Checkout: support SEPA direct debit</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">payments</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Priya Raman" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">PR<span class="sr-only">Priya Raman</span></span>
            <a href="https://github.com/acme/hoverlab/pull/482" class="inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-git-pull-request h-3.5 w-3.5" aria-hidden="true"><circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M13 6h3a2 2 0 0 1 2 2v7"></path><line x1="6" x2="6" y1="9" y2="21"></line></svg>#482</a>
          </div>
        </li>
        <li class="rounded-xl border p-3 transition-colors border-destructive/40 bg-destructive/5">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-amber-500"></span>
            <span>Medium</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-318</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Rotate staging TLS certificates</p>
          <p class="mt-1.5 flex items-center gap-1.5 text-xs font-medium text-destructive"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-octagon-alert h-3.5 w-3.5" aria-hidden="true"><path d="M12 16h.01"></path><path d="M12 8v4"></path><path d="M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"></path></svg>Blocked — Waiting on DNS delegation</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">infra</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Sam Okafor" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">SO<span class="sr-only">Sam Okafor</span></span>
            <time dateTime="2026-08-14" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 14</time>
          </div>
        </li>
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-amber-500"></span>
            <span>Medium</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-325</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">New onboarding checklist</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">growth</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Jonas Weber" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">JW<span class="sr-only">Jonas Weber</span></span>
            <time dateTime="2026-08-15" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 15</time>
          </div>
        </li>
      </ul>
    </section>
    <section aria-label="In review" class="w-72 shrink-0">
      <div class="mb-3 flex items-center gap-2 px-1">
        <h2 class="text-sm font-semibold tracking-tight">In review</h2>
        <span class="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">2</span>
        <button type="button" aria-label="Add task to In review" class="ms-auto inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus h-4 w-4" aria-hidden="true">
            <path d="M5 12h14"></path>
            <path d="M12 5v14"></path>
          </svg>
        </button>
      </div>
      <ul class="space-y-2.5">
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-rose-500"></span>
            <span>High</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-314</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Fix duplicate invoice emails</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">billing</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Rosa Martínez" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">RM<span class="sr-only">Rosa Martínez</span></span>
            <a href="https://github.com/acme/hoverlab/pull/479" class="inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-git-pull-request h-3.5 w-3.5" aria-hidden="true"><circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M13 6h3a2 2 0 0 1 2 2v7"></path><line x1="6" x2="6" y1="9" y2="21"></line></svg>#479</a>
          </div>
        </li>
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-sky-500"></span>
            <span>Low</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-311</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Dashboard loading skeletons</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">design</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Dana Whitfield" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">DW<span class="sr-only">Dana Whitfield</span></span>
            <a href="https://github.com/acme/hoverlab/pull/476" class="inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-git-pull-request h-3.5 w-3.5" aria-hidden="true"><circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M13 6h3a2 2 0 0 1 2 2v7"></path><line x1="6" x2="6" y1="9" y2="21"></line></svg>#476</a>
          </div>
        </li>
      </ul>
    </section>
    <section aria-label="Done" class="w-72 shrink-0">
      <div class="mb-3 flex items-center gap-2 px-1">
        <h2 class="text-sm font-semibold tracking-tight">Done</h2>
        <span class="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">2</span>
        <button type="button" aria-label="Add task to Done" class="ms-auto inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus h-4 w-4" aria-hidden="true">
            <path d="M5 12h14"></path>
            <path d="M12 5v14"></path>
          </svg>
        </button>
      </div>
      <ul class="space-y-2.5">
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-amber-500"></span>
            <span>Medium</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-306</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Upgrade CI runners to Node 22</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">infra</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Alex Chen" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">AC<span class="sr-only">Alex Chen</span></span>
            <time dateTime="2026-08-11" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 11</time>
          </div>
        </li>
        <li class="rounded-xl border p-3 transition-colors border-border/60 bg-card hover:border-border">
          <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
            <span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-rose-500"></span>
            <span>High</span>
            <span class="ms-auto font-mono text-[0.65rem]">HL-302</span>
          </div>
          <p class="mt-1.5 text-sm font-medium leading-snug">Search relevance tuning</p>
          <div class="mt-2 flex flex-wrap gap-1">
            <span class="rounded-md bg-secondary px-1.5 py-0.5 text-[0.65rem] font-medium text-secondary-foreground">search</span>
          </div>
          <div class="mt-2.5 flex items-center justify-between">
            <span title="Priya Raman" class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[0.6rem] font-semibold text-muted-foreground">PR<span class="sr-only">Priya Raman</span></span>
            <time dateTime="2026-08-12" class="inline-flex items-center gap-1 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days h-3.5 w-3.5" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path><path d="M8 14h.01"></path><path d="M12 14h.01"></path><path d="M16 14h.01"></path><path d="M8 18h.01"></path><path d="M12 18h.01"></path><path d="M16 18h.01"></path></svg>Aug 12</time>
          </div>
        </li>
      </ul>
    </section>
  </div>
</div>
  • This is rendered HTML, not a translation of the React source. The Tailwind classes carry the design and work in any framework.
  • It is one frame: the component in its initial state, with no props applied beyond the defaults.
  • Requires Tailwind, and the design tokens the classes reference (bg-card, text-muted-foreground, and so on). The template ZIPs ship a globals.css that defines them.

What each framework gets across the whole catalog — effects convert properly; this rung is markup.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

Used in these pages

Want the whole screen instead of this one section? Open a page and copy it entire.

8 more blocks in Dashboards

All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.

Browse Dashboards

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once

More Dashboards blocks

View category
Open the full page for this block

KPI Cards With Deltas

Metric row where each card knows whether a rise is good news, so churn going up is red while revenue going up is green.

Dashboards114 lines1 dep
Open the full page for this block

Activity Feed Panel

Recent-events list with typed icons and relative timestamps that keep the absolute value in a <time> element.

Dashboards171 lines1 dep
Open the full page for this block

Audit Log Timeline

Events grouped by day on a vertical rail, icons varying by type and one entry expanded into a detail card — what keeps forty events scannable instead of forty gray dots.

Dashboards204 lines1 dep
Open the full page for this block

Saved View Tabs

Saved views with the third state most implementations drop: a shared view with unsaved edits, where Save changes what a colleague sees tomorrow. Two honest buttons instead of one, ownership on the tab, and dirty said in words.

Dashboards306 lines1 dep
Open the full page for this block

Comparison Period Picker

A reporting window with the comparison window spelled out beside it — resolved dates, UTC arithmetic, and an option to align weekdays so a weekend is never compared against a Tuesday.

Dashboards315 lines1 dep
Open the full page for this block

Alert Rule List

Threshold alerts with the two facts most rule lists omit: how often each has fired, and whether its channel is muted — so a rule that is on but delivering nowhere reads as its own state.

Dashboards282 lines1 dep