Law Firm — Firm Dashboard
An authoritative law-firm admin dashboard with KPI cards for active matters, billable hours, revenue and new leads, a CSS bar chart of billable hours by attorney, a leads-by-source breakdown, and a sortable recent-matters table with status badges and a live month filter.
MCP
Code
:root {
--navy: #1b2a4a;
--navy-d: #101b33;
--gold: #b08d57;
--gold-soft: #efe3cf;
--ink: #1a1f2b;
--muted: #5b6577;
--bg: #f5f3ee;
--white: #ffffff;
--line: #e4ded2;
--line-strong: #d6cdbb;
--serif: "Georgia", "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--ok: #2f7d52;
--ok-bg: #e3f0e8;
--warn: #9a6b1f;
--warn-bg: #f4e9d4;
--hold: #7a5cc4;
--hold-bg: #ece6f7;
--neutral: #5b6577;
--neutral-bg: #ebe7df;
--radius: 12px;
--shadow: 0 1px 2px rgba(16, 27, 51, 0.04), 0 8px 24px rgba(16, 27, 51, 0.06);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--sans);
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
h1, h2 {
font-family: var(--serif);
font-weight: 600;
letter-spacing: -0.01em;
color: var(--navy);
}
.shell {
min-height: 100vh;
}
/* Topbar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: 16px clamp(18px, 4vw, 40px);
background: var(--navy);
color: #eef1f6;
border-bottom: 3px solid var(--gold);
}
.brand {
display: flex;
align-items: center;
gap: 14px;
}
.brand-mark {
display: grid;
place-items: center;
width: 46px;
height: 46px;
border-radius: 8px;
background: var(--navy-d);
border: 1px solid rgba(176, 141, 87, 0.5);
color: var(--gold);
font-family: var(--serif);
font-size: 17px;
letter-spacing: 0.02em;
}
.brand-text {
display: flex;
flex-direction: column;
}
.brand-name {
font-family: var(--serif);
font-size: 18px;
letter-spacing: 0.01em;
}
.brand-sub {
font-size: 12px;
color: #b9c2d6;
}
.topbar-controls {
display: flex;
align-items: center;
gap: 22px;
flex-wrap: wrap;
}
.period {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #b9c2d6;
}
.period select {
font-family: var(--sans);
font-size: 14px;
letter-spacing: 0;
text-transform: none;
color: #eef1f6;
background: var(--navy-d);
border: 1px solid rgba(176, 141, 87, 0.45);
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
}
.period select:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
.me {
display: flex;
align-items: center;
gap: 10px;
}
.me-avatar {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 50%;
background: var(--gold-soft);
color: var(--navy);
font-weight: 600;
font-size: 13px;
}
.me-text {
display: flex;
flex-direction: column;
}
.me-name {
font-size: 14px;
font-weight: 600;
}
.me-role {
font-size: 12px;
color: #b9c2d6;
}
/* Content */
.content {
max-width: 1120px;
margin: 0 auto;
padding: clamp(22px, 4vw, 40px);
}
.page-head {
margin-bottom: 22px;
}
.page-head h1 {
font-size: clamp(24px, 4vw, 30px);
}
.page-sub {
font-size: 13px;
color: var(--muted);
margin-top: 4px;
}
/* KPI cards */
.kpis {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
margin-bottom: 26px;
}
.kpi {
display: flex;
flex-direction: column;
gap: 8px;
background: var(--white);
border: 1px solid var(--line);
border-top: 3px solid var(--gold);
border-radius: var(--radius);
padding: 18px 18px 16px;
box-shadow: var(--shadow);
}
.kpi-label {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--muted);
}
.kpi-value {
font-family: var(--serif);
font-size: 30px;
color: var(--navy);
line-height: 1.1;
}
.kpi-delta {
font-size: 12.5px;
font-weight: 500;
}
.kpi-delta.up { color: var(--ok); }
.kpi-delta.down { color: #b4452f; }
/* Grid for chart + leads */
.grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 18px;
margin-bottom: 18px;
}
.panel {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 20px clamp(16px, 3vw, 24px) 22px;
}
.panel-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding-bottom: 12px;
margin-bottom: 16px;
border-bottom: 1px solid var(--gold);
}
.panel-head h2 {
font-size: 17px;
}
.panel-note {
font-size: 12px;
color: var(--muted);
}
/* Bar chart */
.bars {
list-style: none;
display: flex;
flex-direction: column;
gap: 14px;
}
.bar-row {
display: grid;
grid-template-columns: 96px 1fr;
align-items: center;
gap: 12px;
}
.bar-name {
font-size: 13px;
font-weight: 500;
color: var(--ink);
}
.bar-track {
position: relative;
height: 26px;
background: var(--bg);
border-radius: 6px;
overflow: hidden;
}
.bar-fill {
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
height: 100%;
width: var(--pct);
background: linear-gradient(90deg, var(--navy), #2c447a);
border-radius: 6px;
padding-right: 8px;
transform-origin: left;
animation: grow 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.bar-val {
font-size: 12px;
font-weight: 600;
color: var(--gold-soft);
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* Leads breakdown */
.leads {
list-style: none;
display: flex;
flex-direction: column;
gap: 16px;
}
.lead-row {
display: flex;
flex-direction: column;
gap: 5px;
}
.lead-top {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.lead-name {
font-size: 13.5px;
font-weight: 500;
}
.lead-pct {
font-size: 13px;
font-weight: 600;
color: var(--navy);
}
.lead-track {
height: 8px;
background: var(--bg);
border-radius: 99px;
overflow: hidden;
}
.lead-fill {
display: block;
height: 100%;
width: var(--pct);
background: linear-gradient(90deg, var(--gold), #c9a877);
border-radius: 99px;
animation: grow 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
transform-origin: left;
}
.lead-count {
font-size: 11.5px;
color: var(--muted);
}
/* Matters table */
.table-wrap {
overflow-x: auto;
}
.matters {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
.matters th {
text-align: left;
padding: 0;
border-bottom: 1px solid var(--line-strong);
}
.matters th button {
font-family: var(--sans);
font-size: 11.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
background: none;
border: none;
cursor: pointer;
padding: 10px 14px;
width: 100%;
text-align: left;
display: flex;
align-items: center;
gap: 5px;
}
.matters th.num button { justify-content: flex-end; }
.matters th button:hover { color: var(--navy); }
.matters th button:focus-visible {
outline: 2px solid var(--gold);
outline-offset: -2px;
border-radius: 4px;
}
.matters th[aria-sort="ascending"] button::after { content: "▲"; font-size: 9px; color: var(--gold); }
.matters th[aria-sort="descending"] button::after { content: "▼"; font-size: 9px; color: var(--gold); }
.matters td {
padding: 13px 14px;
border-bottom: 1px solid var(--line);
color: var(--ink);
vertical-align: middle;
}
.matters td.num {
text-align: right;
font-variant-numeric: tabular-nums;
font-weight: 500;
}
.matters tbody tr {
transition: background 0.15s ease;
}
.matters tbody tr:hover {
background: #faf8f3;
}
.matters tbody tr:last-child td {
border-bottom: none;
}
/* Status badges */
.badge {
display: inline-block;
font-size: 11.5px;
font-weight: 600;
letter-spacing: 0.02em;
padding: 3px 10px;
border-radius: 99px;
white-space: nowrap;
}
.badge.active { color: var(--ok); background: var(--ok-bg); }
.badge.review { color: var(--warn); background: var(--warn-bg); }
.badge.hold { color: var(--hold); background: var(--hold-bg); }
.badge.closed { color: var(--neutral); background: var(--neutral-bg); }
.disclaimer {
margin-top: 24px;
font-size: 12px;
color: var(--muted);
text-align: center;
}
/* Responsive */
@media (max-width: 920px) {
.kpis { grid-template-columns: repeat(2, 1fr); }
.grid { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.topbar { gap: 14px; }
.topbar-controls { width: 100%; justify-content: space-between; }
.kpis { grid-template-columns: 1fr; gap: 12px; }
.bar-row { grid-template-columns: 80px 1fr; }
.matters thead { display: none; }
.matters, .matters tbody, .matters tr, .matters td { display: block; width: 100%; }
.matters tr {
border: 1px solid var(--line);
border-radius: 10px;
margin-bottom: 12px;
padding: 4px 0;
}
.matters td {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
border-bottom: 1px solid var(--line);
text-align: right;
}
.matters td.num { text-align: right; }
.matters tr td:last-child { border-bottom: none; }
.matters td::before {
content: attr(data-label);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
text-align: left;
}
}
@media (prefers-reduced-motion: reduce) {
.bar-fill, .lead-fill { animation: none; }
}(function () {
"use strict";
// ---- Period-based dataset (re-bases the whole dashboard) ----
var DATA = {
jun: {
label: "June 2026",
kpis: { matters: "68", hours: "1,284", revenue: "$842,500", leads: "37" },
bars: [["M. Pierce", 176], ["D. Okafor", 158], ["R. Castellano", 142], ["A. Velázquez", 128], ["J. Whitfield", 110], ["H. Tanaka", 94]],
leads: [["Referrals", 15], ["Organic search", 10], ["Directory listing", 7], ["Paid ads", 5]]
},
may: {
label: "May 2026",
kpis: { matters: "62", hours: "1,232", revenue: "$759,000", leads: "40" },
bars: [["M. Pierce", 168], ["D. Okafor", 151], ["R. Castellano", 133], ["A. Velázquez", 121], ["J. Whitfield", 118], ["H. Tanaka", 88]],
leads: [["Referrals", 14], ["Organic search", 12], ["Directory listing", 8], ["Paid ads", 6]]
},
apr: {
label: "April 2026",
kpis: { matters: "59", hours: "1,176", revenue: "$701,200", leads: "33" },
bars: [["M. Pierce", 159], ["D. Okafor", 144], ["R. Castellano", 127], ["A. Velázquez", 110], ["J. Whitfield", 102], ["H. Tanaka", 81]],
leads: [["Referrals", 11], ["Organic search", 9], ["Directory listing", 8], ["Paid ads", 5]]
}
};
var select = document.getElementById("period");
var periodLabel = document.getElementById("periodLabel");
var barsEl = document.getElementById("bars");
var leadsEl = document.getElementById("leads");
function fmtTime() {
var d = new Date();
var hh = String(d.getHours()).padStart(2, "0");
var mm = String(d.getMinutes()).padStart(2, "0");
return hh + ":" + mm;
}
function render(key) {
var d = DATA[key];
if (!d) return;
// KPIs
Object.keys(d.kpis).forEach(function (k) {
var el = document.querySelector('[data-kpi="' + k + '"]');
if (el) el.textContent = d.kpis[k];
});
// Bar chart — scale relative to the top performer
var maxBar = Math.max.apply(null, d.bars.map(function (b) { return b[1]; }));
barsEl.innerHTML = d.bars.map(function (b) {
var pct = Math.round((b[1] / maxBar) * 100);
return '<li class="bar-row">' +
'<span class="bar-name">' + b[0] + '</span>' +
'<span class="bar-track"><span class="bar-fill" style="--pct:' + pct + '%">' +
'<span class="bar-val">' + b[1] + '</span></span></span></li>';
}).join("");
// Leads by source
var totalLeads = d.leads.reduce(function (s, l) { return s + l[1]; }, 0);
document.querySelector(".leads-panel .panel-note").textContent = totalLeads + " new leads";
leadsEl.innerHTML = d.leads.map(function (l) {
var pct = Math.round((l[1] / totalLeads) * 100);
return '<li class="lead-row">' +
'<span class="lead-top"><span class="lead-name">' + l[0] + '</span>' +
'<span class="lead-pct">' + pct + '%</span></span>' +
'<span class="lead-track"><span class="lead-fill" style="--pct:' + pct + '%"></span></span>' +
'<span class="lead-count">' + l[1] + ' leads</span></li>';
}).join("");
periodLabel.textContent = "Performance for " + d.label + ", updated 22 Jun 2026 · " + fmtTime();
}
if (select) {
select.addEventListener("change", function () { render(select.value); });
render(select.value);
}
// ---- Sortable matters table ----
var table = document.getElementById("matters");
if (table) {
var tbody = table.querySelector("tbody");
var headers = table.querySelectorAll("th[data-sort]");
headers.forEach(function (th) {
var btn = th.querySelector("button");
btn.addEventListener("click", function () {
var index = Array.prototype.indexOf.call(th.parentNode.children, th);
var isNum = th.dataset.type === "num";
var current = th.getAttribute("aria-sort");
var asc = current !== "ascending";
// reset all headers
headers.forEach(function (h) { h.setAttribute("aria-sort", "none"); });
th.setAttribute("aria-sort", asc ? "ascending" : "descending");
var rows = Array.prototype.slice.call(tbody.querySelectorAll("tr"));
rows.sort(function (a, b) {
var ca = a.children[index];
var cb = b.children[index];
var va, vb;
if (isNum) {
va = parseFloat(ca.dataset.value || "0");
vb = parseFloat(cb.dataset.value || "0");
} else {
va = ca.textContent.trim().toLowerCase();
vb = cb.textContent.trim().toLowerCase();
}
if (va < vb) return asc ? -1 : 1;
if (va > vb) return asc ? 1 : -1;
return 0;
});
rows.forEach(function (r) { tbody.appendChild(r); });
});
});
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sterling & Crowe — Firm Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="shell">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">S&C</span>
<div class="brand-text">
<span class="brand-name">Sterling & Crowe LLP</span>
<span class="brand-sub">Attorneys at Law · Firm Dashboard</span>
</div>
</div>
<div class="topbar-controls">
<label class="period" for="period">
<span>Reporting period</span>
<select id="period" aria-label="Reporting period">
<option value="jun" selected>June 2026</option>
<option value="may">May 2026</option>
<option value="apr">April 2026</option>
</select>
</label>
<div class="me" title="Managing Partner">
<span class="me-avatar" aria-hidden="true">MP</span>
<div class="me-text">
<span class="me-name">Margaret Pierce</span>
<span class="me-role">Managing Partner</span>
</div>
</div>
</div>
</header>
<main class="content">
<div class="page-head">
<h1>Firm overview</h1>
<p class="page-sub" id="periodLabel">Performance for June 2026, updated 22 Jun 2026 · 09:14</p>
</div>
<section class="kpis" aria-label="Key performance indicators">
<article class="kpi">
<span class="kpi-label">Active matters</span>
<span class="kpi-value" data-kpi="matters">68</span>
<span class="kpi-delta up">▲ 6 vs. last period</span>
</article>
<article class="kpi">
<span class="kpi-label">Billable hours (month)</span>
<span class="kpi-value" data-kpi="hours">1,284</span>
<span class="kpi-delta up">▲ 4.2% vs. last period</span>
</article>
<article class="kpi">
<span class="kpi-label">Revenue collected</span>
<span class="kpi-value" data-kpi="revenue">$842,500</span>
<span class="kpi-delta up">▲ 11% vs. last period</span>
</article>
<article class="kpi">
<span class="kpi-label">New leads</span>
<span class="kpi-value" data-kpi="leads">37</span>
<span class="kpi-delta down">▼ 3 vs. last period</span>
</article>
</section>
<div class="grid">
<section class="panel chart-panel" aria-labelledby="chartTitle">
<div class="panel-head">
<h2 id="chartTitle">Billable hours by attorney</h2>
<span class="panel-note">Hours logged this period</span>
</div>
<ul class="bars" id="bars">
<li class="bar-row">
<span class="bar-name">M. Pierce</span>
<span class="bar-track"><span class="bar-fill" style="--pct:88%"><span class="bar-val">176</span></span></span>
</li>
<li class="bar-row">
<span class="bar-name">D. Okafor</span>
<span class="bar-track"><span class="bar-fill" style="--pct:79%"><span class="bar-val">158</span></span></span>
</li>
<li class="bar-row">
<span class="bar-name">R. Castellano</span>
<span class="bar-track"><span class="bar-fill" style="--pct:71%"><span class="bar-val">142</span></span></span>
</li>
<li class="bar-row">
<span class="bar-name">A. Velázquez</span>
<span class="bar-track"><span class="bar-fill" style="--pct:64%"><span class="bar-val">128</span></span></span>
</li>
<li class="bar-row">
<span class="bar-name">J. Whitfield</span>
<span class="bar-track"><span class="bar-fill" style="--pct:55%"><span class="bar-val">110</span></span></span>
</li>
<li class="bar-row">
<span class="bar-name">H. Tanaka</span>
<span class="bar-track"><span class="bar-fill" style="--pct:47%"><span class="bar-val">94</span></span></span>
</li>
</ul>
</section>
<section class="panel leads-panel" aria-labelledby="leadsTitle">
<div class="panel-head">
<h2 id="leadsTitle">Leads by source</h2>
<span class="panel-note">37 new leads</span>
</div>
<ul class="leads" id="leads">
<li class="lead-row">
<span class="lead-top"><span class="lead-name">Referrals</span><span class="lead-pct">41%</span></span>
<span class="lead-track"><span class="lead-fill" style="--pct:41%"></span></span>
<span class="lead-count">15 leads</span>
</li>
<li class="lead-row">
<span class="lead-top"><span class="lead-name">Organic search</span><span class="lead-pct">27%</span></span>
<span class="lead-track"><span class="lead-fill" style="--pct:27%"></span></span>
<span class="lead-count">10 leads</span>
</li>
<li class="lead-row">
<span class="lead-top"><span class="lead-name">Directory listing</span><span class="lead-pct">19%</span></span>
<span class="lead-track"><span class="lead-fill" style="--pct:19%"></span></span>
<span class="lead-count">7 leads</span>
</li>
<li class="lead-row">
<span class="lead-top"><span class="lead-name">Paid ads</span><span class="lead-pct">13%</span></span>
<span class="lead-track"><span class="lead-fill" style="--pct:13%"></span></span>
<span class="lead-count">5 leads</span>
</li>
</ul>
</section>
</div>
<section class="panel table-panel" aria-labelledby="tableTitle">
<div class="panel-head">
<h2 id="tableTitle">Recent matters</h2>
<span class="panel-note">Click a column to sort</span>
</div>
<div class="table-wrap">
<table class="matters" id="matters">
<thead>
<tr>
<th scope="col" data-sort="matter" aria-sort="none"><button type="button">Matter</button></th>
<th scope="col" data-sort="client" aria-sort="none"><button type="button">Client</button></th>
<th scope="col" data-sort="area" aria-sort="none"><button type="button">Practice area</button></th>
<th scope="col" data-sort="attorney" aria-sort="none"><button type="button">Lead attorney</button></th>
<th scope="col" class="num" data-sort="value" data-type="num" aria-sort="none"><button type="button">Value</button></th>
<th scope="col" data-sort="status" aria-sort="none"><button type="button">Status</button></th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Matter">M-2041 · Acquisition review</td>
<td data-label="Client">Halcyon Robotics</td>
<td data-label="Practice area">Corporate / M&A</td>
<td data-label="Lead attorney">M. Pierce</td>
<td data-label="Value" class="num" data-value="320000">$320,000</td>
<td data-label="Status"><span class="badge active">Active</span></td>
</tr>
<tr>
<td data-label="Matter">M-2038 · Trademark dispute</td>
<td data-label="Client">Bramble & Co.</td>
<td data-label="Practice area">IP / Litigation</td>
<td data-label="Lead attorney">D. Okafor</td>
<td data-label="Value" class="num" data-value="148000">$148,000</td>
<td data-label="Status"><span class="badge review">In review</span></td>
</tr>
<tr>
<td data-label="Matter">M-2035 · Lease negotiation</td>
<td data-label="Client">Northgate Holdings</td>
<td data-label="Practice area">Real Estate</td>
<td data-label="Lead attorney">R. Castellano</td>
<td data-label="Value" class="num" data-value="62500">$62,500</td>
<td data-label="Status"><span class="badge active">Active</span></td>
</tr>
<tr>
<td data-label="Matter">M-2033 · Estate settlement</td>
<td data-label="Client">Estate of E. Hartwell</td>
<td data-label="Practice area">Trusts & Estates</td>
<td data-label="Lead attorney">A. Velázquez</td>
<td data-label="Value" class="num" data-value="89000">$89,000</td>
<td data-label="Status"><span class="badge hold">On hold</span></td>
</tr>
<tr>
<td data-label="Matter">M-2029 · Employment claim</td>
<td data-label="Client">Vireo Logistics</td>
<td data-label="Practice area">Labor & Employment</td>
<td data-label="Lead attorney">J. Whitfield</td>
<td data-label="Value" class="num" data-value="54000">$54,000</td>
<td data-label="Status"><span class="badge active">Active</span></td>
</tr>
<tr>
<td data-label="Matter">M-2024 · Contract closeout</td>
<td data-label="Client">Meridian Foods</td>
<td data-label="Practice area">Corporate</td>
<td data-label="Lead attorney">H. Tanaka</td>
<td data-label="Value" class="num" data-value="41000">$41,000</td>
<td data-label="Status"><span class="badge closed">Closed</span></td>
</tr>
<tr>
<td data-label="Matter">M-2019 · Patent filing</td>
<td data-label="Client">Quanta Devices</td>
<td data-label="Practice area">IP / Patents</td>
<td data-label="Lead attorney">D. Okafor</td>
<td data-label="Value" class="num" data-value="76500">$76,500</td>
<td data-label="Status"><span class="badge review">In review</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<p class="disclaimer">Illustrative UI only — not for real legal, billing or matter-management use.</p>
</main>
</div>
<script src="script.js"></script>
</body>
</html>Firm Dashboard
A trust-first administrative overview for a law firm, built in the authoritative navy-and-gold house style with serif headings and generous whitespace. Four KPI cards lead with active matters, billable hours this month, collected revenue and new leads — each with a period-over-period delta.
Below, a pure-CSS bar chart compares billable hours by attorney, a leads-by-source breakdown shows where new business originates, and a recent-matters table lists each engagement with practice area, responsible attorney, value and a colour-coded status badge.
A month selector at the top re-bases every figure on the dashboard, and the matters table is sortable by clicking a column header. All interactions are vanilla JavaScript with no libraries.
Illustrative UI only — not for real legal, billing or matter-management use.