Law Firm — Attorney Roster
An authoritative law-firm team page with a searchable grid of attorney cards showing initials avatars, titles, practice tags and bar admissions, live filters by practice area and seniority, a result count, and click-to-expand quick bio panels driven by vanilla JS.
MCP
Code
:root {
--navy: #1b2a4a;
--navy-d: #101b33;
--gold: #b08d57;
--gold-soft: #efe3cf;
--ink: #1a1f2b;
--muted: #5b6577;
--bg: #f5f3ee;
--white: #ffffff;
--line: #e3ddd1;
--serif: "Georgia", "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--radius: 10px;
--shadow: 0 1px 2px rgba(16, 27, 51, 0.06), 0 8px 24px rgba(16, 27, 51, 0.07);
}
* { box-sizing: border-box; }
body, h1, h2, p, ul, li, figure { margin: 0; padding: 0; }
body {
font-family: var(--sans);
background: var(--bg);
color: var(--ink);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
.roster {
max-width: 1040px;
margin: 0 auto;
padding: 56px 24px 80px;
}
/* Masthead */
.masthead { text-align: center; margin-bottom: 40px; }
.eyebrow {
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--gold);
font-weight: 600;
}
.masthead h1 {
font-family: var(--serif);
font-weight: 700;
font-size: clamp(2rem, 5vw, 2.9rem);
color: var(--navy);
margin: 10px 0 12px;
letter-spacing: -0.01em;
}
.lede {
max-width: 560px;
margin: 0 auto;
color: var(--muted);
font-size: 1.02rem;
}
.rule {
display: block;
width: 64px; height: 2px;
background: var(--gold);
margin: 26px auto 0;
}
/* Controls */
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px 28px;
padding: 20px 22px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.search {
position: relative;
flex: 1 1 260px;
}
.search-icon {
position: absolute;
left: 14px; top: 50%;
transform: translateY(-50%);
color: var(--gold);
font-size: 1.15rem;
pointer-events: none;
}
.search input {
width: 100%;
font-family: var(--sans);
font-size: 0.95rem;
color: var(--ink);
padding: 11px 14px 11px 40px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--bg);
transition: border-color 0.15s, box-shadow 0.15s;
}
.search input::placeholder { color: #9aa1ad; }
.search input:focus {
outline: none;
border-color: var(--gold);
box-shadow: 0 0 0 3px var(--gold-soft);
}
.filter-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.filter-label {
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
}
.pills { display: flex; flex-wrap: wrap; gap: 6px; }
.pill {
font-family: var(--sans);
font-size: 0.82rem;
font-weight: 500;
color: var(--navy);
background: var(--bg);
border: 1px solid var(--line);
border-radius: 999px;
padding: 6px 13px;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.pill:hover { border-color: var(--gold); }
.pill:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--gold-soft);
}
.pill.is-active {
background: var(--navy);
border-color: var(--navy);
color: var(--white);
}
.count {
margin: 24px 2px 16px;
font-size: 0.86rem;
color: var(--muted);
}
.count strong { color: var(--navy); }
/* Grid */
.grid {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 18px;
}
.card {
background: var(--white);
border: 1px solid var(--line);
border-top: 3px solid var(--gold);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: transform 0.18s, box-shadow 0.18s;
}
.card.is-open { box-shadow: 0 4px 14px rgba(16,27,51,0.12), 0 18px 40px rgba(16,27,51,0.12); }
.card-head {
display: flex;
width: 100%;
text-align: left;
gap: 16px;
align-items: center;
padding: 20px;
background: none;
border: 0;
cursor: pointer;
font-family: var(--sans);
}
.card-head:hover { background: #fcfbf8; }
.card-head:focus-visible {
outline: none;
box-shadow: inset 0 0 0 3px var(--gold-soft);
}
.avatar {
flex: 0 0 auto;
width: 56px; height: 56px;
display: grid;
place-items: center;
border-radius: 50%;
background: linear-gradient(145deg, var(--navy), var(--navy-d));
color: var(--gold-soft);
font-family: var(--serif);
font-weight: 700;
font-size: 1.25rem;
letter-spacing: 0.02em;
}
.who { min-width: 0; flex: 1; }
.name {
font-family: var(--serif);
font-size: 1.12rem;
color: var(--navy);
font-weight: 700;
line-height: 1.2;
}
.title {
display: inline-block;
margin-top: 4px;
font-size: 0.74rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--gold);
}
.title.is-partner { color: var(--navy); }
.chev {
flex: 0 0 auto;
color: var(--muted);
font-size: 0.9rem;
transition: transform 0.2s;
}
.card.is-open .chev { transform: rotate(180deg); }
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 0 20px 18px;
}
.tag {
font-size: 0.73rem;
font-weight: 500;
color: var(--navy);
background: var(--gold-soft);
border-radius: 5px;
padding: 3px 9px;
}
.bio {
display: none;
padding: 0 20px 20px;
border-top: 1px solid var(--line);
margin-top: 2px;
}
.card.is-open .bio { display: block; }
.bio .bio-inner { padding-top: 16px; }
.bio p { font-size: 0.9rem; color: var(--muted); margin-bottom: 12px; }
.bars { font-size: 0.78rem; color: var(--ink); margin-bottom: 10px; }
.bars span { color: var(--muted); }
.contact {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.82rem;
font-weight: 600;
color: var(--navy);
text-decoration: none;
border-bottom: 1px solid var(--gold);
padding-bottom: 1px;
}
.contact:hover { color: var(--gold); }
.empty {
text-align: center;
color: var(--muted);
font-size: 0.95rem;
padding: 48px 16px;
border: 1px dashed var(--line);
border-radius: var(--radius);
margin-top: 8px;
}
@media (max-width: 520px) {
.roster { padding: 36px 16px 56px; }
.controls { padding: 16px; gap: 14px; }
.filter-group { width: 100%; }
.grid { grid-template-columns: 1fr; }
.card-head { padding: 16px; gap: 12px; }
.avatar { width: 48px; height: 48px; font-size: 1.1rem; }
}(function () {
"use strict";
var ATTORNEYS = [
{
name: "Margaret Halloran",
title: "Managing Partner",
seniority: "Partner",
practices: ["Corporate", "Tax"],
bars: ["New York", "Connecticut", "U.S. Tax Court"],
email: "[email protected]",
bio: "Founding partner with 28 years advising closely held companies on mergers, succession and cross-border tax structuring. Lead counsel on more than 60 acquisitions."
},
{
name: "Daniel Reyes",
title: "Partner",
seniority: "Partner",
practices: ["Litigation"],
bars: ["New York", "New Jersey", "2nd Circuit"],
email: "[email protected]",
bio: "Trial lawyer focused on complex commercial disputes and appellate work. Has argued before the Second Circuit eleven times and tried matters in state and federal court."
},
{
name: "Priya Nair",
title: "Partner",
seniority: "Partner",
practices: ["Real Estate", "Corporate"],
bars: ["New York", "Massachusetts"],
email: "[email protected]",
bio: "Heads the real estate group, handling acquisitions, development financing and commercial leasing for institutional clients and family offices across the Northeast."
},
{
name: "Robert Whitfield",
title: "Of Counsel",
seniority: "Of Counsel",
practices: ["Tax", "Corporate"],
bars: ["New York", "Florida", "U.S. Tax Court"],
email: "[email protected]",
bio: "Former IRS senior counsel who advises on partnership taxation, audits and voluntary disclosures. Joined the firm after 19 years in government practice."
},
{
name: "Elena Castellano",
title: "Senior Associate",
seniority: "Associate",
practices: ["Family"],
bars: ["New York", "New Jersey"],
email: "[email protected]",
bio: "Concentrates on high-net-worth divorce, prenuptial agreements and custody mediation, with a measured, settlement-first approach that keeps matters out of court."
},
{
name: "James Okonkwo",
title: "Senior Associate",
seniority: "Associate",
practices: ["Litigation", "Corporate"],
bars: ["New York", "Pennsylvania"],
email: "[email protected]",
bio: "Represents technology and manufacturing clients in contract disputes, trade-secret claims and shareholder litigation. Recognized in the 2025 Rising Stars list."
},
{
name: "Hannah Lieberman",
title: "Associate",
seniority: "Associate",
practices: ["Real Estate"],
bars: ["New York"],
email: "[email protected]",
bio: "Supports the real estate team on due diligence, title review and lease negotiation for retail and multifamily portfolios throughout the tri-state area."
},
{
name: "Marcus Tran",
title: "Associate",
seniority: "Associate",
practices: ["Family", "Litigation"],
bars: ["New York", "Connecticut"],
email: "[email protected]",
bio: "Handles family-law motions, support enforcement and protective orders. Volunteers weekly at the county legal aid clinic representing pro bono clients."
}
];
var state = { q: "", practice: "all", seniority: "all" };
var grid = document.getElementById("grid");
var empty = document.getElementById("empty");
var countN = document.getElementById("count-n");
var search = document.getElementById("search");
var countNoun = document.getElementById("count-noun");
function initials(name) {
return name.split(/\s+/).map(function (w) { return w[0]; }).join("").slice(0, 2).toUpperCase();
}
function matches(a) {
if (state.practice !== "all" && a.practices.indexOf(state.practice) === -1) return false;
if (state.seniority !== "all" && a.seniority !== state.seniority) return false;
if (state.q) {
var hay = (a.name + " " + a.title + " " + a.practices.join(" ")).toLowerCase();
if (hay.indexOf(state.q) === -1) return false;
}
return true;
}
function render() {
var list = ATTORNEYS.filter(matches);
grid.innerHTML = "";
list.forEach(function (a, i) {
var li = document.createElement("li");
li.className = "card";
var partnerCls = a.seniority === "Partner" ? " is-partner" : "";
var panelId = "bio-" + i;
var tags = a.practices.map(function (p) {
return '<span class="tag">' + p + "</span>";
}).join("");
li.innerHTML =
'<button class="card-head" type="button" aria-expanded="false" aria-controls="' + panelId + '">' +
'<span class="avatar" aria-hidden="true">' + initials(a.name) + "</span>" +
'<span class="who">' +
'<span class="name">' + a.name + "</span><br>" +
'<span class="title' + partnerCls + '">' + a.title + "</span>" +
"</span>" +
'<span class="chev" aria-hidden="true">▾</span>' +
"</button>" +
'<div class="card-tags">' + tags + "</div>" +
'<div class="bio" id="' + panelId + '" role="region">' +
'<div class="bio-inner">' +
"<p>" + a.bio + "</p>" +
'<p class="bars"><strong>Bar admissions:</strong> <span>' + a.bars.join(", ") + "</span></p>" +
'<a class="contact" href="mailto:' + a.email + '">✉ ' + a.email + "</a>" +
"</div>" +
"</div>";
var head = li.querySelector(".card-head");
head.addEventListener("click", function () {
var open = li.classList.toggle("is-open");
head.setAttribute("aria-expanded", open ? "true" : "false");
});
grid.appendChild(li);
});
countN.textContent = String(list.length);
countNoun.textContent = list.length === 1 ? "attorney" : "attorneys";
empty.hidden = list.length !== 0;
grid.hidden = list.length === 0;
}
function wirePills(group) {
var key = group.getAttribute("data-filter");
group.querySelectorAll(".pill").forEach(function (btn) {
btn.addEventListener("click", function () {
group.querySelectorAll(".pill").forEach(function (b) {
b.classList.remove("is-active");
b.setAttribute("aria-pressed", "false");
});
btn.classList.add("is-active");
btn.setAttribute("aria-pressed", "true");
state[key] = btn.getAttribute("data-value");
render();
});
});
}
document.querySelectorAll(".pills").forEach(wirePills);
search.addEventListener("input", function () {
state.q = search.value.trim().toLowerCase();
render();
});
render();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Attorney Roster — Halloran & Reyes LLP</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<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>
<main class="roster" aria-labelledby="roster-title">
<header class="masthead">
<p class="eyebrow">Halloran & Reyes LLP</p>
<h1 id="roster-title">Our Attorneys</h1>
<p class="lede">
Counsel built on decades of courtroom and boardroom experience across corporate,
litigation and private-client practice.
</p>
<span class="rule" aria-hidden="true"></span>
</header>
<section class="controls" aria-label="Filter attorneys">
<div class="search">
<label class="visually-hidden" for="search">Search attorneys</label>
<span class="search-icon" aria-hidden="true">⌕</span>
<input
type="search"
id="search"
placeholder="Search by name, title or practice…"
autocomplete="off"
/>
</div>
<div class="filter-group" role="group" aria-label="Practice area">
<span class="filter-label">Practice</span>
<div class="pills" data-filter="practice">
<button class="pill is-active" data-value="all" aria-pressed="true">All</button>
<button class="pill" data-value="Corporate" aria-pressed="false">Corporate</button>
<button class="pill" data-value="Litigation" aria-pressed="false">Litigation</button>
<button class="pill" data-value="Real Estate" aria-pressed="false">Real Estate</button>
<button class="pill" data-value="Tax" aria-pressed="false">Tax</button>
<button class="pill" data-value="Family" aria-pressed="false">Family</button>
</div>
</div>
<div class="filter-group" role="group" aria-label="Seniority">
<span class="filter-label">Seniority</span>
<div class="pills" data-filter="seniority">
<button class="pill is-active" data-value="all" aria-pressed="true">All</button>
<button class="pill" data-value="Partner" aria-pressed="false">Partner</button>
<button class="pill" data-value="Of Counsel" aria-pressed="false">Of Counsel</button>
<button class="pill" data-value="Associate" aria-pressed="false">Associate</button>
</div>
</div>
</section>
<p class="count" aria-live="polite"><strong id="count-n">8</strong> <span id="count-noun">attorneys</span></p>
<ul class="grid" id="grid" aria-label="Attorney roster"></ul>
<p class="empty" id="empty" hidden>No attorneys match your filters.</p>
</main>
<script src="script.js"></script>
</body>
</html>Attorney Roster
A trust-first team page for a law firm. The roster leads with a serif-headed masthead and a thin gold rule, then a search field and two pill filters — Practice area and Seniority — that narrow a responsive grid of attorney cards. Each card carries an initials avatar, the attorney’s name and title (Partner, Of Counsel, Senior Associate, Associate), their practice-area tags and bar admissions.
Typing in the search box matches names, titles and practice areas live, while the filters combine with it; a result count updates on every change and an empty state appears when nothing matches. Clicking a card expands an inline bio panel with a short profile and contact line, and clicking again collapses it — all keyboard-accessible with real focus styles.
The palette uses a navy and gold law-firm system with Georgia headings and Inter body text, generous whitespace and thin gold rules for an authoritative, established feel.
Illustrative UI only — not affiliated with any real law firm or attorney.