Dashboard Responsive Grid Pattern (TPL-M01)
Dashboard Responsive Grid Pattern (TPL-M01)
All dashboard stat-card sections must use a consistent responsive grid layout. This contract ensures:
- The loading skeleton and live content share identical grid shapes, preventing cumulative layout shift (CLS) during hydration.
- AI agents generating new stat sections always produce correct responsive classes.
Required Grid Classes
| Use case | className |
|---|---|
| 4-up stat cards | grid gap-4 sm:grid-cols-2 lg:grid-cols-4 |
| 2-up panels | grid gap-4 sm:grid-cols-2 |
| 2-up wide cards | grid gap-6 lg:grid-cols-2 |
Example
// Correct ✅ — 4-up stat card row
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<StatCard title="MRR" value="$12,400" />
<StatCard title="Churn" value="2.1%" />
<StatCard title="Active Users" value="348" />
<StatCard title="NPS" value="62" />
</div>
// Incorrect ❌ — plain flex row breaks on mobile
<div className="flex flex-row gap-4">
<StatCard ... />
</div>
// Incorrect ❌ — vertical stack loses the grid benefit
<div className="flex flex-col gap-4">
<StatCard ... />
</div>
Skeleton Alignment
loading.tsx renders a DashboardSkeleton that uses the identical class strings. If you change the grid class on any live section, update the corresponding skeleton placeholder to match, or users will see a layout jump when content loads.
Nested Components
If a child component (e.g. OverviewStats) already renders its own grid gap-4 sm:grid-cols-2 lg:grid-cols-4 wrapper internally, do not add a second outer grid wrapper at the route level — this creates a double-nested grid that breaks the layout. Add a doc comment at the route level instead, as done for <OverviewStats /> in dashboard/page.tsx.
Where the Contract Lives
The canonical reference comment appears at the top of both:
src/app/dashboard/page.tsx— live applicationtemplate/src/app/dashboard/page.tsx— product template
Any AI agent or engineer adding a new stat section to the dashboard should read that comment before generating grid markup.