Dashboard Layout
App-shell pattern built on SidebarLayout — persistent sidebar on desktop, slide-in overlay on mobile, with stat cards, chart, and activity feed.
Live Preview
Key Features
- SidebarLayout as the app shell — responsive sidebar with mobile hamburger overlay
- Stat cards with equal height and baseline-aligned values
- Tooltip with explicit placement to avoid Floating-UI drift on tight grids
- Revenue chart with consistent month-label spacing
- Activity feed stacked full-width under the chart
- Active-route highlighting on sidebar navigation
Code
Dashboard Layout Recipe
<script lang="ts">
import {
Avatar, Badge, BarChart, Button, Card, Progress, Tooltip,
SidebarLayout, MenuIcon, HomeIcon, BarChartIcon, UsersIcon, SettingsIcon
} from '@urbicon-ui/blocks';
let sidebarOpen = $state(false);
let activeRoute = $state('dashboard');
const navItems = [
{ id: 'dashboard', label: 'Dashboard', icon: HomeIcon },
{ id: 'analytics', label: 'Analytics', icon: BarChartIcon },
{ id: 'users', label: 'Users', icon: UsersIcon },
{ id: 'settings', label: 'Settings', icon: SettingsIcon }
];
const stats = [
{ label: 'Total Users', value: '12,847', change: '+12.5%', intent: 'success' as const, progress: 78, tooltip: 'Target: 16,500 users by Q2' },
{ label: 'Revenue', value: '$48,392', change: '+8.2%', intent: 'success' as const, progress: 64, tooltip: 'Target: $75,000 monthly' },
{ label: 'Active Sessions', value: '1,429', change: '-3.1%', intent: 'danger' as const, progress: 45, tooltip: 'Down from 1,475 last week' },
{ label: 'Conversion', value: '3.24%', change: '+0.8%', intent: 'success' as const, progress: 81, tooltip: 'Target: 4.0% conversion rate' }
];
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const chartData = [40, 65, 45, 80, 55, 70, 85, 60, 90, 75, 95, 88];
const revenue = months.map((label, i) => ({ label, values: [chartData[i]] }));
const recentActivity = [
{ user: 'Sarah Chen', action: 'Purchased Pro plan', time: '2 min ago' },
{ user: 'Marcus Rivera', action: 'Submitted support ticket', time: '15 min ago' },
{ user: 'Aisha Patel', action: 'Updated profile settings', time: '1 hour ago' },
{ user: 'Tom Weber', action: 'Exported analytics report', time: '2 hours ago' },
{ user: 'Lisa Kim', action: 'Added team member', time: '3 hours ago' }
];
</script>
<SidebarLayout bind:open={sidebarOpen} sidebarWidth="14rem" data-design-pattern="dashboard">
{#snippet sidebarHeader()}
<a href="/" class="flex h-14 items-center px-4 font-bold text-primary">Acme Inc</a>
{/snippet}
{#snippet sidebar()}
<nav aria-label="Dashboard sidebar" class="flex flex-col gap-1 p-3">
{#each navItems as item (item.id)}
{@const Icon = item.icon}
<button
type="button"
onclick={() => (activeRoute = item.id)}
class={activeRoute === item.id
? 'flex items-center gap-2.5 rounded-lg bg-primary-subtle px-3 py-2 text-sm font-medium text-primary'
: 'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary'}
>
<Icon class="h-4 w-4 shrink-0" />
<span>{item.label}</span>
</button>
{/each}
</nav>
{/snippet}
{#snippet sidebarFooter()}
<div class="flex items-center gap-3 p-3">
<Avatar name="Mara Cohen" size="sm" />
<div class="min-w-0">
<div class="truncate text-xs font-medium text-text-primary">Mara C</div>
<div class="truncate text-3xs text-text-tertiary">Admin</div>
</div>
</div>
{/snippet}
{#snippet mobileHeader({ openSidebar })}
<Button variant="ghost" size="sm" onclick={openSidebar} aria-label="Open menu">
<MenuIcon class="h-5 w-5" />
</Button>
<span class="font-semibold">Acme Inc</span>
{/snippet}
<div class="space-y-6">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold text-text-primary">Dashboard</h1>
<p class="text-sm text-text-tertiary">Welcome back, Mara</p>
</div>
<Button size="sm" intent="primary">Export Report</Button>
</div>
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
{#each stats as stat (stat.label)}
<Card class="h-full" variant="elevated">
<Tooltip label={stat.tooltip} placement="top">
<div class="flex flex-col gap-2">
<p class="min-h-8 text-xs font-medium text-text-tertiary">{stat.label}</p>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold tabular-nums text-text-primary">{stat.value}</span>
<Badge variant="soft" intent={stat.intent} size="sm">{stat.change}</Badge>
</div>
<Progress
value={stat.progress}
size="xs"
intent={stat.intent === 'danger' ? 'danger' : 'primary'}
/>
</div>
</Tooltip>
</Card>
{/each}
</div>
<Card>
<div class="space-y-4">
<h2 class="text-sm font-semibold text-text-primary">Revenue Overview</h2>
<BarChart
data={revenue}
series={[{ label: 'Revenue' }]}
height={192}
showLegend={false}
ariaLabel="Monthly revenue"
/>
</div>
</Card>
<Card>
<div class="space-y-4">
<h2 class="text-sm font-semibold text-text-primary">Recent Activity</h2>
<div class="space-y-3">
{#each recentActivity as activity (activity.user)}
<div class="flex items-start gap-3">
<Avatar name={activity.user} size="sm" />
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-text-primary">{activity.user}</p>
<p class="text-xs text-text-tertiary">{activity.action} · {activity.time}</p>
</div>
</div>
{/each}
</div>
</div>
</Card>
</div>
</SidebarLayout>