Sidebar Dashboard Shell
Sidebar, top bar and scrollable content slot, with a mobile drawer that closes on Escape and marks the active route.
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.
components/kanban-board.tsxnpx hoverlab add kanban-board
Or over MCP, from your editor's agent — no account needed.
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.
Start a page with this section — add more, order them, and leave with the page source.
Rate-limit the export endpoint
Empty-state illustrations for search
Migrate billing webhooks to v2
Checkout: support SEPA direct debit
Rotate staging TLS certificates
Blocked — Waiting on DNS delegation
New onboarding checklist
Upgrade CI runners to Node 22
Search relevance tuning
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
/**
* <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>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/kanban-board.tsx and import it where you need the section:
import { KanbanBoard } from '@/components/kanban-board'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.
| Prop | Type | Default |
|---|---|---|
columns | KanbanColumn[] | DEFAULT_COLUMNS, className = '' |
className | string | — |
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 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>
What each framework gets across the whole catalog — effects convert properly; this rung is markup.
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.
Want the whole screen instead of this one section? Open a page and copy it entire.
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 DashboardsCopying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.
Sidebar, top bar and scrollable content slot, with a mobile drawer that closes on Escape and marks the active route.
Metric row where each card knows whether a rise is good news, so churn going up is red while revenue going up is green.
Recent-events list with typed icons and relative timestamps that keep the absolute value in a <time> element.
Breadcrumb, title, action buttons and a counted tab row that sits flush with the bottom rule.
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.
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.
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.
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.