New

Analytics Dashboard

A pattern for composing KPI tiles, charts, and tables into a cohesive analytics screen with a sticky time-range header. Mirrors Warren's InsightsView and JobFlo's AnalyticsView.

Demo

Analytics
7 Days30 Days90 DaysAll Time

Overview

+12%
1,284
Applications
+3%
34%
Interview Rate
-1.4d
8.2d
Avg Response
+2
6
Active Pipelines

Stage Funnel

Applied
1,284
Screening
438
Interview
182
Offer
34
Hired
12

Layout spec

ZoneSpec
Sticky header56 px, view title + time-range Segmented Button, --background backing, bottom divider
KPI rowEqual-width tiles in HStack, gap --spacing-lg; collapses to 2-col on narrow
Primary chart zone1fr flexible chart + 300 px companion (donut or breakdown list)
Section headers11 px semibold, letter-spacing 0.8, --text-tertiary, uppercase — matches label-mono
Empty statesInline EmptyState per chart tile; never full-page replacement
Dashboard background--background, not --surface

Time range picker

Use the Segmented Button locked to these options: 7 Days / 30 Days / 90 Days / All Time. Place it in the sticky header row, right-aligned. The picker drives all chart and KPI data on the same screen.

Implementation

AnalyticsDashboard.tsx
tsx
"use client";

import { useState } from "react";

type TimeRange = "7d" | "30d" | "90d" | "all";

interface KPIData {
  label: string;
  value: string;
  delta?: string;
  trend?: "up" | "down" | "flat";
  icon?: React.ReactNode;
}

export function AnalyticsDashboard() {
  const [range, setRange] = useState<TimeRange>("30d");

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
      {/* Sticky header */}
      <div style={{
        position: "sticky",
        top: 0,
        height: 56,
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        padding: "0 20px",
        background: "rgb(var(--background))",
        borderBottom: "1px solid rgb(var(--border))",
        zIndex: 10,
      }}>
        <h1 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>Analytics</h1>
        <TimeRangePicker value={range} onChange={setRange} />
      </div>

      <div style={{ padding: 20, display: "flex", flexDirection: "column", gap: 20 }}>
        {/* KPI row */}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12 }}>
          <KPITile label="Total Applications" value="1,284" delta="+12%" trend="up" />
          <KPITile label="Interview Rate" value="34%" delta="+3%" trend="up" />
          <KPITile label="Avg. Response Time" value="8.2d" delta="-1.4d" trend="up" />
          <KPITile label="Active Pipelines" value="6" delta="+2" trend="up" />
        </div>

        {/* Primary chart zone */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 300px", gap: 12 }}>
          <ChartTile title="Applications Over Time">
            {/* Chart content */}
          </ChartTile>
          <ChartTile title="Stage Breakdown">
            {/* Donut / breakdown */}
          </ChartTile>
        </div>
      </div>
    </div>
  );
}

function TimeRangePicker({ value, onChange }: { value: TimeRange; onChange: (v: TimeRange) => void }) {
  const options: [TimeRange, string][] = [["7d","7 Days"],["30d","30 Days"],["90d","90 Days"],["all","All Time"]];
  return (
    <div style={{ display: "flex", gap: 2, padding: 3, borderRadius: 8, background: "rgb(var(--surface-raised))", border: "1px solid rgb(var(--border))" }}>
      {options.map(([key, label]) => (
        <button key={key} onClick={() => onChange(key)} style={{
          padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 500,
          border: "none", cursor: "pointer",
          background: value === key ? "rgb(var(--accent))" : "transparent",
          color: value === key ? "#fff" : "rgb(var(--text-secondary))",
          transition: "background 0.15s",
        }}>{label}</button>
      ))}
    </div>
  );
}