Real-World Project
Build a small, responsive analytics dashboard page from scratch, combining grid layout, dark mode, cards, and everything else learned in this course.
Introduction
This final lesson is a single, connected build rather than a set of isolated examples. You will assemble a small analytics dashboard page using a sidebar, a top bar, a responsive grid of stat cards, and a working dark mode toggle — combining Flexbox, Grid, spacing, theming, and the component patterns from earlier lessons into one realistic page.
- A fixed sidebar with navigation links.
- A top bar with a page title and a dark mode toggle.
- A responsive grid of stat cards that reflows from 1 to 4 columns.
- A reusable Card component styled consistently for both light and dark mode.
Planning the Layout
The page splits into three regions: a sidebar fixed to the left on desktop, a top bar spanning the remaining width, and a main content area below it holding the stat cards. On mobile, the sidebar collapses out of the flow entirely to keep the page usable on a small screen.
The Page Shell
Start with an outer flex container that lays the sidebar and the main content area side by side, with dark mode background colors set from the very first element.
// app/dashboard/page.tsxexport default function Dashboard() { return ( <div className="flex min-h-screen bg-gray-50 dark:bg-gray-900"> {/* Sidebar */} {/* Main content */} </div> );}The Sidebar
The sidebar is hidden on small screens and becomes a fixed-width flex column from md upward — the same hidden md:flex pattern used for the navbar earlier in this course.
<aside className="hidden md:flex md:w-64 md:flex-col border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6"> <span className="text-xl font-bold text-gray-900 dark:text-white mb-8">PrograMinds</span> <nav className="flex flex-col gap-1"> <a className="rounded-md px-3 py-2 text-sm font-medium bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400"> Dashboard </a> <a className="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"> Courses </a> <a className="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"> Students </a> <a className="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"> Settings </a> </nav></aside>The Top Bar
The main column wraps the top bar and the page content. The top bar uses the familiar flex items-center justify-between pattern to place the page title on the left and a dark mode toggle button on the right.
<div className="flex-1 flex flex-col"> <header className="flex h-16 items-center justify-between border-b border-gray-200 dark:border-gray-800 px-6"> <h1 className="text-lg font-semibold text-gray-900 dark:text-white">Overview</h1> <button onClick={() => document.documentElement.classList.toggle('dark')} className="rounded-md p-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800" > 🌙 </button> </header>
<main className="flex-1 p-6"> {/* stat cards go here */} </main></div>Stat Cards with Grid
The stat row uses CSS Grid with responsive column counts: a single column on mobile, two on small tablets, and four across on desktop, all with a consistent gap.
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> <StatCard label="Active Students" value="12,480" change="+8.2%" /> <StatCard label="Courses Published" value="27" change="+2" /> <StatCard label="Avg. Completion" value="76%" change="+4.1%" /> <StatCard label="Monthly Revenue" value="$18,240" change="+12.5%" /></div>Click Run to see what this code prints.
A Reusable Card Component
Following the "when to extract a component" guidance from the previous lesson — this pattern repeats four times on this page alone — StatCard is a genuine candidate for extraction, built with the clsx merging pattern from the React/Next.js lesson.
// components/StatCard.tsximport clsx from 'clsx';
interface StatCardProps { label: string; value: string; change: string; className?: string;}
export default function StatCard({ label, value, change, className }: StatCardProps) { const isPositive = change.startsWith('+');
return ( <div className={clsx( 'p-6 rounded-lg border transition-shadow hover:shadow-md', 'bg-white border-gray-200', 'dark:bg-gray-800 dark:border-gray-700', className )} > <p className="text-sm font-medium text-gray-500 dark:text-gray-400">{label}</p> <p className="mt-2 text-2xl font-bold text-gray-900 dark:text-white">{value}</p> <p className={clsx( 'mt-1 text-sm font-medium', isPositive ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' )} > {change} this month </p> </div> );}Wiring Up Dark Mode
The toggle button in the top bar calls document.documentElement.classList.toggle('dark'), which adds or removes the dark class on <html>. Every dark: utility used across the sidebar, top bar, and StatCard responds to that single class automatically — this is class-based dark mode, the same strategy set up earlier in the course.
// tailwind.config.jsmodule.exports = { darkMode: 'class', content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], theme: { extend: {} }, plugins: [],};Because every element on this page pairs its light-mode utility with a dark: counterpart, toggling a single class on <html> restyles the entire dashboard — sidebar, top bar, and every stat card — with no per-component logic needed.
Making It Responsive
Putting the full page together, notice how every responsive decision reuses patterns from earlier lessons: hidden md:flex collapses the sidebar, and grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 reflows the stat cards — the same responsive vocabulary applied consistently across completely different elements.
// Full structure, condensed<div className="flex min-h-screen bg-gray-50 dark:bg-gray-900"> <aside className="hidden md:flex md:w-64 md:flex-col ...">...</aside> <div className="flex-1 flex flex-col"> <header className="flex h-16 items-center justify-between ...">...</header> <main className="flex-1 p-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> <StatCard label="Active Students" value="12,480" change="+8.2%" /> <StatCard label="Courses Published" value="27" change="+2" /> <StatCard label="Avg. Completion" value="76%" change="+4.1%" /> <StatCard label="Monthly Revenue" value="$18,240" change="+12.5%" /> </div> </main> </div></div>Common Mistakes
- Adding dark: utilities to some elements but not others, leaving parts of the page stuck in light mode after toggling.
- Forgetting darkMode: 'class' in tailwind.config.js, which makes every dark: utility target the OS-level color scheme instead of the toggle button.
- Hard-coding four stat cards in a row without responsive grid-cols, breaking the layout on mobile.
- Duplicating the card markup four times inline instead of extracting StatCard once the repetition was obvious.
Best Practices
- Pair every color, background, and border utility with a dark: counterpart from the start, rather than retrofitting dark mode later.
- Reuse the same hidden md:flex and grid-cols-* responsive patterns across a page instead of inventing a new approach per component.
- Extract a component as soon as a card or row pattern repeats more than two or three times, exactly as StatCard did here.
- Keep the content array in tailwind.config.js accurate as new folders like components/ are added.
Frequently Asked Questions
Toggling a class on document.documentElement works well for a simple example and persists visually without extra plumbing. A production app would typically sync this with React state and localStorage so the preference survives a page reload.
Yes — a two-column CSS Grid (grid-cols-[16rem_1fr]) is an equally valid alternative to the flex shell used here; both are common, valid Tailwind approaches to a sidebar layout.
No, that is plain JavaScript/TypeScript logic; Tailwind only handles which classes get applied once isPositive is computed, via the clsx conditional shown in the component.
Key Takeaways
- A real page is just a composition of the same small set of patterns — flex shells, responsive grids, and dark: pairs — reused deliberately.
- Class-based dark mode restyles an entire page from a single toggled class once every element carries a dark: counterpart.
- Extracting StatCard once its repetition became obvious kept the page's markup clean and consistent.
- Responsive design in Tailwind is about choosing a small number of breakpoints and applying them consistently, not adding overrides everywhere.
Summary
You built a complete, responsive, dark-mode-aware dashboard page by combining Flexbox, Grid, theming, component extraction, and conditional classes — every major concept from this course working together in one realistic layout.