All Taste components
Dashboards
Analytics overview
Website traffic at a glance: visitors against the previous period, where they came from and the pages that convert.
top nav · 4 KPIs · area chart with comparison · donut · pages table with bars
PPulse
MNVisitors
1,28,460
↑ 18.2% vs prior 30 days
Page views
4,02,115
↑ 9.6% vs prior 30 days
Bounce rate
41.3%
↑ 2.1 pts vs prior 30 days
Avg. visit
2m 48s
↑ 14s vs prior 30 days
Visitors
16 Sep – 15 OctPrevious period
Top sources
42%
from search
Search53,950
Social34,680
Direct23,120
Email16,710
Top pages
| Page | Share of visitors | Visitors | Avg. time | Conversion |
|---|---|---|---|---|
| / | 61,240 | 1m 12s | 2.8% | |
| /collections/filter-coffee | 28,190 | 3m 05s | 6.4% | |
| /products/malabar-pepper-200g | 19,030 | 2m 41s | 8.1% | |
| /blog/how-to-store-spices | 13,480 | 4m 22s | 1.2% |
import { Calendar, ChevronDown, Globe } from "lucide-react";
import { cn } from "@/lib/utils";
const tabs = ["Overview", "Pages", "Sources", "Funnels", "Events"];
const kpis = [
{ label: "Visitors", value: "1,28,460", change: "↑ 18.2%", good: true },
{ label: "Page views", value: "4,02,115", change: "↑ 9.6%", good: true },
{ label: "Bounce rate", value: "41.3%", change: "↑ 2.1 pts", good: false },
{ label: "Avg. visit", value: "2m 48s", change: "↑ 14s", good: true },
];
// Daily visitors in thousands, this period and the previous one.
const visits = [3.1, 3.4, 3.2, 3.8, 4.1, 3.0, 2.7, 3.6, 3.9, 4.2, 4.0, 4.6, 3.4, 3.1, 4.3, 4.8, 4.5, 5.1, 4.9, 3.8, 3.6, 4.9, 5.3, 5.0, 5.6, 5.4, 4.2, 4.0, 5.5, 5.9];
const previous = [2.8, 3.0, 2.9, 3.2, 3.4, 2.6, 2.4, 3.1, 3.3, 3.5, 3.4, 3.7, 2.9, 2.7, 3.6, 3.8, 3.7, 4.0, 3.9, 3.1, 2.9, 3.9, 4.1, 4.0, 4.2, 4.1, 3.4, 3.2, 4.2, 4.4];
const sources = [
{ name: "Search", visitors: "53,950", share: 42, swatch: "bg-primary" },
{ name: "Social", visitors: "34,680", share: 27, swatch: "bg-primary/60" },
{ name: "Direct", visitors: "23,120", share: 18, swatch: "bg-primary/30" },
{ name: "Email", visitors: "16,710", share: 13, swatch: "bg-surface-2" },
];
const pages = [
{ path: "/", share: 100, visitors: "61,240", time: "1m 12s", conversion: "2.8%" },
{ path: "/collections/filter-coffee", share: 46, visitors: "28,190", time: "3m 05s", conversion: "6.4%" },
{ path: "/products/malabar-pepper-200g", share: 31, visitors: "19,030", time: "2m 41s", conversion: "8.1%" },
{ path: "/blog/how-to-store-spices", share: 22, visitors: "13,480", time: "4m 22s", conversion: "1.2%" },
];
function toPath(values: number[], max = 7) {
return values.map((v, i) => (i ? "L" : "M") + (i / (values.length - 1)) * 100 + " " + (100 - (v / max) * 100)).join(" ");
}
export default function AnalyticsPage() {
const line = toPath(visits);
return (
<div className="min-h-screen bg-bg font-sans text-sm text-fg">
<header className="flex h-14 items-center gap-5 border-b border-border px-4 sm:px-6">
<span className="flex items-center gap-2 font-medium">
<span className="grid size-6 place-items-center rounded-sm bg-primary text-xs font-semibold text-primary-fg">P</span>Pulse
</span>
<nav aria-label="Sections" className="hidden h-full gap-4 md:flex">
{tabs.map((t, i) => (
<a key={t} href="#" aria-current={i === 0 ? "page" : undefined}
className={cn("flex items-center border-b-2 border-transparent text-[13px] font-medium text-fg-muted hover:text-fg",
i === 0 && "border-primary text-fg")}>{t}</a>
))}
</nav>
<div className="ml-auto flex items-center gap-2">
<button type="button" className="hidden h-8 items-center gap-1.5 rounded border border-border-strong px-2.5 text-[13px] font-medium sm:inline-flex">
<Globe className="size-3.5" aria-hidden /> shop.kaveri.in <ChevronDown className="size-3.5" aria-hidden />
</button>
<button type="button" className="inline-flex h-8 items-center gap-1.5 rounded border border-border-strong px-2.5 text-[13px] font-medium">
<Calendar className="size-3.5" aria-hidden /> Last 30 days <ChevronDown className="size-3.5" aria-hidden />
</button>
</div>
</header>
<main className="grid gap-4 p-4 sm:p-6">
<h1 className="sr-only">Analytics overview</h1>
<section aria-label="Key figures" className="grid grid-cols-2 gap-3 xl:grid-cols-4">
{kpis.map((k) => (
<div key={k.label} className="rounded-lg border border-border p-4">
<p className="text-xs text-fg-muted">{k.label}</p>
<p className="mt-1 text-2xl font-medium tracking-tight tabular-nums">{k.value}</p>
<p className="mt-1 text-xs">
<span className={cn("font-medium tabular-nums", k.good ? "text-success-fg" : "text-danger-fg")}>{k.change}</span>
<span className="text-fg-subtle"> vs prior 30 days</span>
</p>
</div>
))}
</section>
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_300px]">
<section aria-labelledby="visitors-title" className="rounded-lg border border-border">
<div className="flex flex-wrap items-center gap-3 border-b border-border px-4 py-3">
<h2 id="visitors-title" className="font-medium">Visitors</h2>
<div className="ml-auto flex gap-3 text-xs text-fg-muted">
<span className="flex items-center gap-1.5"><i className="size-2 rounded-sm bg-primary" />16 Sep – 15 Oct</span>
<span className="flex items-center gap-1.5"><i className="size-2 rounded-sm bg-border-strong" />Previous period</span>
</div>
</div>
<svg viewBox="0 0 100 100" preserveAspectRatio="none" className="h-56 w-full p-4" role="img" aria-label="Daily visitors over the last 30 days">
<defs>
<linearGradient id="fill" x1="0" x2="0" y1="0" y2="1">
<stop offset="0" className="[stop-color:var(--tst-primary)] [stop-opacity:.22]" />
<stop offset="1" className="[stop-color:var(--tst-primary)] [stop-opacity:0]" />
</linearGradient>
</defs>
<path d={line + " L100 100 L0 100 Z"} fill="url(#fill)" />
<path d={toPath(previous)} fill="none" vectorEffect="non-scaling-stroke" strokeDasharray="4 3" className="stroke-border-strong" strokeWidth={1.5} />
<path d={line} fill="none" vectorEffect="non-scaling-stroke" className="stroke-primary" strokeWidth={2} />
</svg>
</section>
<section aria-labelledby="sources-title" className="rounded-lg border border-border">
<h2 id="sources-title" className="border-b border-border px-4 py-3 font-medium">Top sources</h2>
<ul className="grid gap-3 p-4">
{sources.map((s) => (
<li key={s.name} className="grid gap-1.5">
<div className="flex items-center gap-2">
<i className={cn("size-2 rounded-sm", s.swatch)} />{s.name}
<span className="ml-auto tabular-nums text-fg-muted">{s.visitors}</span>
</div>
<div className="h-1.5 rounded-full bg-surface-2"><div className={cn("h-full rounded-full", s.swatch)} style={{ width: s.share + "%" }} /></div>
</li>
))}
</ul>
</section>
</div>
<section aria-labelledby="pages-title" className="overflow-hidden rounded-lg border border-border">
<h2 id="pages-title" className="border-b border-border px-4 py-3 font-medium">Top pages</h2>
<div className="overflow-x-auto">
<table className="w-full text-[13px]">
<thead className="bg-surface text-left text-xs text-fg-subtle">
<tr>
<th className="px-4 py-2 font-medium">Page</th><th className="w-1/3 px-4 py-2 font-medium">Share of visitors</th>
<th className="px-4 py-2 text-right font-medium">Visitors</th><th className="px-4 py-2 text-right font-medium">Avg. time</th>
<th className="px-4 py-2 text-right font-medium">Conversion</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{pages.map((p) => (
<tr key={p.path} className="whitespace-nowrap">
<td className="px-4 py-2.5 font-medium">{p.path}</td>
<td className="px-4 py-2.5"><div className="h-1.5 rounded-full bg-surface-2"><div className="h-full rounded-full bg-primary" style={{ width: p.share + "%" }} /></div></td>
<td className="px-4 py-2.5 text-right tabular-nums">{p.visitors}</td>
<td className="px-4 py-2.5 text-right tabular-nums text-fg-muted">{p.time}</td>
<td className="px-4 py-2.5 text-right font-medium tabular-nums">{p.conversion}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</main>
</div>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.