Capstone: Build a Responsive TaskFlow Dashboard
Combine the course concepts into a complete dashboard with navigation, responsive cards, filters, task rows, states, and theme-aware styling.
Plan the dashboard structure
Build the dashboard in layers:
- Application shell: page background, navigation, content container.
- Page header: title, summary, primary action.
- Metric cards: consistent grid, number hierarchy, descriptive labels.
- Task controls: search, filter, sort, and status indicators.
- Task list: readable rows, due dates, owners, and completion state.
- Responsive behavior: stack on narrow screens; introduce sidebars or columns only when content has room.
Use semantic landmarks such as <header>, <nav>, <main>, and sections with meaningful headings. Use realistic data and test long task titles, empty results, and error states.
- Build the shell before individual cards.
- Use semantic landmarks and a logical heading hierarchy.
- Test realistic content and empty/error states.
Implement the dashboard interface
The following is a compact capstone skeleton. Extend it with application state, real data, loading and error states, and your routing framework. It focuses on layout and visual hierarchy rather than pretending to implement a complete task-management backend.
This skeleton demonstrates presentation. A production app still needs functional search and filters, task creation, loading/error/empty states, routing, data validation, and a working theme toggle.
const metrics = [
{ label: "Open tasks", value: "18", note: "Across 4 projects" },
{ label: "Completed this week", value: "42", note: "+8 from last week" },
{ label: "Overdue", value: "3", note: "Needs attention" },
];
const tasks = [
{ id: 1, title: "Review responsive navigation", project: "Website refresh", status: "In progress" },
{ id: 2, title: "Document component variants", project: "Design system", status: "To do" },
{ id: 3, title: "Test keyboard focus states", project: "Accessibility", status: "Completed" },
];
export default function TaskFlowDashboard() {
return (
<div className="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
<header className="border-b border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6">
<a href="/" className="text-lg font-bold tracking-tight">TaskFlow</a>
<button className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
Create task
</button>
</div>
</header>
<main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:py-10">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<p className="text-sm font-semibold text-blue-700 dark:text-blue-300">Workspace overview</p>
<h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">Good morning, team.</h1>
<p className="mt-2 text-slate-600 dark:text-slate-300">Here is what needs your attention today.</p>
</div>
</div>
<section aria-label="Task metrics" className="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
{metrics.map((metric) => (
<article key={metric.label} className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<p className="text-sm text-slate-600 dark:text-slate-300">{metric.label}</p>
<p className="mt-2 text-3xl font-bold">{metric.value}</p>
<p className="mt-2 text-sm text-slate-500 dark:text-slate-400">{metric.note}</p>
</article>
))}
</section>
<section className="mt-8 rounded-2xl border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
<div className="flex flex-col gap-3 border-b border-slate-200 p-5 sm:flex-row sm:items-center sm:justify-between dark:border-slate-800">
<div>
<h2 className="text-lg font-semibold">My tasks</h2>
<p className="mt-1 text-sm text-slate-600 dark:text-slate-300">Priorities across your projects</p>
</div>
<label className="block">
<span className="sr-only">Search tasks</span>
<input type="search" placeholder="Search tasks..." className="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-950 sm:w-64" />
</label>
</div>
<ul className="divide-y divide-slate-200 dark:divide-slate-800">
{tasks.map((task) => (
<li key={task.id} className="flex flex-col gap-2 p-5 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0">
<h3 className="break-words font-medium">{task.title}</h3>
<p className="mt-1 text-sm text-slate-600 dark:text-slate-300">{task.project}</p>
</div>
<span className="w-fit rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-700 dark:bg-slate-800 dark:text-slate-200">{task.status}</span>
</li>
))}
</ul>
</section>
</main>
</div>
);
}Review checklist and extension challenges
Use this checklist to assess the project:
- Does the page work at 320 px without horizontal page overflow?
- Are grid columns introduced only when the content fits?
- Do buttons and links use the correct native elements?
- Is keyboard focus clearly visible?
- Do labels and search inputs have accessible names?
- Are status meanings communicated with text, not color alone?
- Does dark mode preserve contrast for borders, text, inputs, and focus?
- Does reduced-motion preference disable nonessential animation?
- Are class candidates statically discoverable?
- Does the production build render the same intended styles?
Extension challenges
- Add a filter toolbar that wraps on small screens.
- Add an empty state with a clear next action.
- Create
Button,Badge,MetricCard, andTaskRowcomponents with finite variants. - Add a theme toggle with persistence and no hydration flash.
- Add a responsive sidebar that becomes a drawer on mobile.
- Audit contrast and keyboard flow with browser tools.
- A capstone should prove responsive, accessible, and maintainable behavior—not only visual polish.
- Treat empty, loading, and error states as first-class UI.
- Use the checklist as a repeatable review before shipping.