Dashboard page layout
A stats row, a pair of charts, an activity block, and a free-form slot for everything else.
<script setup lang="ts">
import DashboardPageLayout from '@shared/components/common/pages/DashboardPageLayout.vue'
import StatCard from '@shared/components/common/dashboard/StatCard.vue'
import BarChart from '@shared/components/common/charts/BarChart.vue'
import DonutChart from '@shared/components/common/charts/DonutChart.vue'
import SaListGroup from '@shared/components/common/list/SaListGroup.vue'
import SaListGroupItem from '@shared/components/common/list/SaListGroupItem.vue'
import { Card, CardContent, CardHeader, CardTitle } from '@shared/components/ui/card'
import { CreditCard, Package, TrendingUp, Users } from 'lucide-vue-next'
const categories = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
const series = [{ name: 'Orders', data: [182, 231, 196, 274, 312] }]
const mix = [
{ name: 'Direct', value: 4820 },
{ name: 'Search', value: 3610 },
{ name: 'Referral', value: 1940 },
]
</script>
<template>
<DashboardPageLayout title="Sales">
<template #description>
This week against the last.
</template>
<template #stats>
<StatCard label="Revenue" value="$48,210" :icon="TrendingUp" accent="emerald" />
<StatCard label="Orders" value="1,195" :icon="Package" accent="sky" />
<StatCard label="Customers" value="842" :icon="Users" accent="violet" />
<StatCard label="Refunds" value="$1,020" :icon="CreditCard" accent="rose" />
</template>
<template #charts>
<Card>
<CardHeader>
<CardTitle>Orders this week</CardTitle>
</CardHeader>
<CardContent>
<BarChart :categories="categories" :series="series" :height="220" />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Where they came from</CardTitle>
</CardHeader>
<CardContent>
<DonutChart :data="mix" :height="220" />
</CardContent>
</Card>
</template>
<template #activity>
<Card>
<CardHeader>
<CardTitle>Latest</CardTitle>
</CardHeader>
<CardContent class="p-0">
<SaListGroup variant="flush">
<SaListGroupItem title="INV-2043 paid" description="Northwind, $1,240" />
<SaListGroupItem title="Order placed" description="Lumen Retail, $486" />
</SaListGroup>
</CardContent>
</Card>
</template>
</DashboardPageLayout>
</template>Usage
Four slots, each with its own grid:
| Slot | Grid |
|---|---|
stats | One column, two from sm, four from xl. |
charts | One column, two from lg. |
activity | Full width. |
default | No grid. Lay it out yourself. |
The stats row goes four-up at xl, not at lg. With a 256-pixel sidebar, a 1024-wide window leaves 646 for the row, which is 149 pixels a card, and a stat card needs roughly 240 before its figure fits beside the icon tile.
None of the thirteen dashboards that ship fills a named slot. Every one of them opens with DashboardGreeting and then lays its own rows out in the default slot, because the uneven splits of a richer dashboard cannot use a fixed two-column grid. The named slots are there for the straightforward case, and they are kept correct so the next dashboard does not inherit a wrong grid.
The header is optional
With no title and no header slots, no header is rendered at all. That is what the shipped dashboards rely on: they pass nothing and open with DashboardGreeting instead. Without the guard the header would draw an empty <h1> with the pin floating beside it.
API
Source: src/shared/components/common/pages/DashboardPageLayout.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Forwarded to the page header. Omit it and the header is not rendered. |
Emits
None.
Slots
| Slot | Description |
|---|---|
breadcrumb | Forwarded to the page header. |
description | Forwarded to the page header. |
actions | Forwarded to the page header. |
stats | The KPI row. |
charts | A two-column chart row. |
activity | A full-width block. |
default | Free-form rows, after the named blocks. |
Accessibility
- With no header, the page has no
<h1>of its own, so give the first block a heading, which is what the dashboard greeting does. - Each block is spacing rather than a landmark, so give the cards inside them their own headings.