Law Firm — Legal Invoice
An authoritative law-firm invoice with a firm header, client and matter block, itemized attorney time entries, expenses, tax and total due, a payment-status badge, a live recalculating subtotal, and a Pay now action with print-friendly styles.
MCP
Code
:root {
--navy: #1b2a4a;
--navy-d: #101b33;
--gold: #b08d57;
--gold-soft: #efe3cf;
--ink: #1a1f2b;
--muted: #5b6577;
--bg: #f5f3ee;
--white: #ffffff;
--line: #e3ddcf;
--serif: "Georgia", "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--shadow: 0 18px 48px rgba(16, 27, 51, 0.12);
--radius: 4px;
}
* {
box-sizing: border-box;
margin: 0;
}
body {
font-family: var(--sans);
color: var(--ink);
background: var(--bg);
line-height: 1.5;
padding: 32px 16px;
-webkit-font-smoothing: antialiased;
}
.invoice {
max-width: 780px;
margin: 0 auto;
background: var(--white);
border: 1px solid var(--line);
border-top: 4px solid var(--navy);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 40px 44px 32px;
}
/* Firm header */
.firm {
display: flex;
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
align-items: flex-start;
}
.firm__brand {
display: flex;
gap: 16px;
align-items: center;
}
.firm__mark {
width: 56px;
height: 56px;
flex: 0 0 56px;
display: grid;
place-items: center;
background: var(--navy);
color: var(--gold);
font-family: var(--serif);
font-size: 1.05rem;
letter-spacing: 0.04em;
border-radius: var(--radius);
}
.firm__name {
font-family: var(--serif);
font-size: 1.6rem;
font-weight: 700;
color: var(--navy);
letter-spacing: 0.01em;
}
.firm__tag {
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--gold);
margin-top: 2px;
}
.firm__addr {
font-style: normal;
font-size: 0.82rem;
color: var(--muted);
text-align: right;
line-height: 1.7;
}
.rule {
border: 0;
border-top: 2px solid var(--gold);
margin: 24px 0;
opacity: 0.7;
}
/* Meta + parties */
.meta {
display: grid;
grid-template-columns: 1.3fr 1fr;
gap: 32px;
}
.block-h {
font-size: 0.72rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--gold);
font-weight: 700;
margin-bottom: 10px;
}
.block-h--section {
margin-top: 30px;
padding-bottom: 6px;
border-bottom: 1px solid var(--line);
}
.party__name {
font-family: var(--serif);
font-size: 1.1rem;
color: var(--navy);
font-weight: 700;
}
.party__line {
font-size: 0.86rem;
color: var(--muted);
}
.matter {
margin-top: 14px;
display: grid;
gap: 6px;
}
.matter div,
.invmeta__grid div {
display: grid;
grid-template-columns: 130px 1fr;
gap: 8px;
font-size: 0.82rem;
}
.matter dt,
.invmeta__grid dt {
color: var(--muted);
}
.matter dd,
.invmeta__grid dd {
color: var(--ink);
font-weight: 500;
}
.invmeta {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 16px 18px;
}
.invmeta__grid {
display: grid;
gap: 6px;
}
.invmeta__grid div {
grid-template-columns: 100px 1fr;
}
.status {
margin-top: 14px;
}
.status__badge {
display: inline-block;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 5px 12px;
border-radius: 999px;
border: 1px solid transparent;
}
.status__badge--unpaid {
background: #fbeaea;
color: #9b2c2c;
border-color: #f1c9c9;
}
.status__badge--partial {
background: var(--gold-soft);
color: #8a6a35;
border-color: #e3cfa9;
}
.status__badge--paid {
background: #e6f4ec;
color: #1f7a48;
border-color: #bfe3cd;
}
/* Ledger tables */
.ledger {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 0.85rem;
}
.ledger thead th {
text-align: left;
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
font-weight: 700;
padding: 8px 10px;
border-bottom: 1.5px solid var(--navy);
}
.ledger td {
padding: 11px 10px;
border-bottom: 1px solid var(--line);
vertical-align: top;
color: var(--ink);
}
.ledger tbody tr {
transition: background 0.15s ease;
}
.ledger tbody tr:hover {
background: #faf8f3;
}
.ledger__desc {
width: auto;
color: var(--ink);
}
.num {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.amt {
font-weight: 600;
color: var(--navy);
}
abbr {
text-decoration: none;
font-weight: 700;
color: var(--navy);
cursor: help;
}
.ledger--exp td:first-child {
white-space: nowrap;
}
/* Totals */
.summary {
display: flex;
justify-content: flex-end;
margin-top: 24px;
}
.totals {
width: 320px;
display: grid;
gap: 2px;
}
.totals > div {
display: flex;
justify-content: space-between;
padding: 7px 0;
font-size: 0.88rem;
}
.totals dt {
color: var(--muted);
}
.totals dd {
font-variant-numeric: tabular-nums;
font-weight: 600;
color: var(--ink);
}
.totals__paid dd {
color: #1f7a48;
}
.totals__due {
margin-top: 8px;
padding: 14px 14px !important;
background: var(--navy);
border-radius: var(--radius);
}
.totals__due dt {
color: var(--gold-soft);
font-family: var(--serif);
font-size: 1rem;
}
.totals__due dd {
color: var(--white);
font-family: var(--serif);
font-size: 1.25rem;
}
/* Footer */
.foot {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 24px;
flex-wrap: wrap;
margin-top: 8px;
}
.foot__terms {
font-size: 0.8rem;
color: var(--muted);
max-width: 60%;
}
.foot__terms strong {
color: var(--ink);
}
.foot__remit {
margin-top: 6px;
font-size: 0.74rem;
}
.foot__actions {
display: flex;
gap: 10px;
}
.btn {
font-family: var(--sans);
font-size: 0.85rem;
font-weight: 600;
padding: 11px 20px;
border-radius: var(--radius);
border: 1px solid transparent;
cursor: pointer;
transition: transform 0.12s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.btn:focus-visible {
outline: 3px solid var(--gold);
outline-offset: 2px;
}
.btn:active {
transform: translateY(1px);
}
.btn--ghost {
background: var(--white);
color: var(--navy);
border-color: var(--navy);
}
.btn--ghost:hover {
background: var(--bg);
}
.btn--pay {
background: var(--gold);
color: var(--navy-d);
}
.btn--pay:hover {
background: #c29a60;
box-shadow: 0 6px 16px rgba(176, 141, 87, 0.4);
}
.btn--pay:disabled {
background: #d6d0c4;
color: var(--muted);
cursor: default;
box-shadow: none;
}
/* Responsive */
@media (max-width: 520px) {
body {
padding: 16px 8px;
}
.invoice {
padding: 24px 18px;
}
.firm,
.meta,
.foot {
grid-template-columns: 1fr;
flex-direction: column;
text-align: left;
}
.firm__addr {
text-align: left;
}
.meta {
gap: 22px;
}
.firm__name {
font-size: 1.35rem;
}
.ledger,
.ledger thead th,
.ledger td {
font-size: 0.8rem;
}
.ledger td,
.ledger thead th {
padding: 9px 6px;
}
.totals {
width: 100%;
}
.foot__terms {
max-width: 100%;
}
.foot__actions {
width: 100%;
}
.btn {
flex: 1;
}
}
/* Print-friendly */
@media print {
body {
background: var(--white);
padding: 0;
}
.invoice {
box-shadow: none;
border: none;
max-width: 100%;
padding: 0;
}
.foot__actions {
display: none;
}
.ledger tbody tr:hover {
background: transparent;
}
.totals__due {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}(function () {
"use strict";
var TAX_RATE = 0.06875;
var usd = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
var timeBody = document.getElementById("timeBody");
var expBody = document.getElementById("expBody");
var feeSubEl = document.getElementById("feeSub");
var expSubEl = document.getElementById("expSub");
var taxEl = document.getElementById("taxAmt");
var totalEl = document.getElementById("totalDue");
var badge = document.getElementById("statusBadge");
var paidRow = document.getElementById("paidRow");
var paidAmtEl = document.getElementById("paidAmt");
var payBtn = document.getElementById("payBtn");
var printBtn = document.getElementById("printBtn");
var paid = 0; // amount paid so far
function round(n) {
return Math.round(n * 100) / 100;
}
// Fill per-row amounts and return the section subtotal.
function fillTimeRows() {
var subtotal = 0;
var rows = timeBody ? timeBody.querySelectorAll("tr") : [];
rows.forEach(function (row) {
var hours = parseFloat(row.getAttribute("data-hours")) || 0;
var rate = parseFloat(row.getAttribute("data-rate")) || 0;
var amount = round(hours * rate);
subtotal += amount;
var cell = row.querySelector(".amt");
if (cell) cell.textContent = usd.format(amount);
});
return round(subtotal);
}
function fillExpenseRows() {
var subtotal = 0;
var rows = expBody ? expBody.querySelectorAll("tr") : [];
rows.forEach(function (row) {
var amount = parseFloat(row.getAttribute("data-amount")) || 0;
subtotal += amount;
var cell = row.querySelector(".amt");
if (cell) cell.textContent = usd.format(amount);
});
return round(subtotal);
}
function setStatus(state, balance, grand) {
badge.classList.remove(
"status__badge--unpaid",
"status__badge--partial",
"status__badge--paid"
);
if (state === "paid") {
badge.classList.add("status__badge--paid");
badge.textContent = "Paid";
} else if (state === "partial") {
badge.classList.add("status__badge--partial");
badge.textContent = "Partially paid";
} else {
badge.classList.add("status__badge--unpaid");
badge.textContent = "Unpaid";
}
}
function recalc() {
var fees = fillTimeRows();
var expenses = fillExpenseRows();
var tax = round((fees + expenses) * TAX_RATE);
var grand = round(fees + expenses + tax);
var balance = round(grand - paid);
feeSubEl.textContent = usd.format(fees);
expSubEl.textContent = usd.format(expenses);
taxEl.textContent = usd.format(tax);
totalEl.textContent = usd.format(balance > 0 ? balance : 0);
if (paid > 0) {
paidRow.hidden = false;
paidAmtEl.textContent = "−" + usd.format(paid);
} else {
paidRow.hidden = true;
}
if (balance <= 0.005) {
setStatus("paid", balance, grand);
payBtn.disabled = true;
payBtn.textContent = "Paid in full";
} else if (paid > 0) {
setStatus("partial", balance, grand);
} else {
setStatus("unpaid", balance, grand);
}
return grand;
}
// Simulate a payment: settle the full outstanding balance.
function handlePay() {
var grand = recalc();
payBtn.disabled = true;
payBtn.textContent = "Processing…";
setTimeout(function () {
paid = grand;
recalc();
}, 700);
}
if (payBtn) payBtn.addEventListener("click", handlePay);
if (printBtn)
printBtn.addEventListener("click", function () {
window.print();
});
recalc();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Legal Invoice — Whitcombe & Associates</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="invoice" aria-labelledby="inv-title">
<!-- Firm header -->
<header class="firm">
<div class="firm__brand">
<div class="firm__mark" aria-hidden="true">W&A</div>
<div>
<h1 class="firm__name" id="inv-title">Whitcombe & Associates</h1>
<p class="firm__tag">Attorneys & Counselors at Law</p>
</div>
</div>
<address class="firm__addr">
1200 Marquette Avenue, Suite 1400<br />
Minneapolis, MN 55403<br />
(612) 555-0184 · [email protected]
</address>
</header>
<hr class="rule" />
<!-- Invoice meta + parties -->
<section class="meta" aria-label="Invoice details">
<div class="parties">
<h2 class="block-h">Billed to</h2>
<p class="party__name">Harborline Logistics, Inc.</p>
<p class="party__line">Attn: Dana Ortega, General Counsel</p>
<p class="party__line">450 Commerce Pkwy, St. Paul, MN 55101</p>
<dl class="matter">
<div><dt>Matter</dt><dd>Vendor contract dispute — Acme Freight</dd></div>
<div><dt>Matter No.</dt><dd>HBL-2024-0317</dd></div>
<div><dt>Responsible attorney</dt><dd>Marcus Whitcombe, Esq.</dd></div>
</dl>
</div>
<div class="invmeta">
<h2 class="block-h">Invoice</h2>
<dl class="invmeta__grid">
<div><dt>Invoice No.</dt><dd>INV-2026-0488</dd></div>
<div><dt>Issue date</dt><dd>Jun 22, 2026</dd></div>
<div><dt>Due date</dt><dd>Jul 22, 2026</dd></div>
<div><dt>Billing period</dt><dd>May 1 – May 31, 2026</dd></div>
</dl>
<p class="status" id="status">
<span class="status__badge status__badge--unpaid" id="statusBadge" role="status">Unpaid</span>
</p>
</div>
</section>
<!-- Time entries -->
<section aria-labelledby="time-h">
<h2 class="block-h block-h--section" id="time-h">Professional services</h2>
<table class="ledger">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Atty</th>
<th scope="col" class="ledger__desc">Description</th>
<th scope="col" class="num">Hours</th>
<th scope="col" class="num">Rate</th>
<th scope="col" class="num">Amount</th>
</tr>
</thead>
<tbody id="timeBody">
<tr data-hours="2.4" data-rate="525">
<td>May 3</td><td><abbr title="Marcus Whitcombe">MW</abbr></td>
<td class="ledger__desc">Review vendor agreement and dispute correspondence; outline strategy memo.</td>
<td class="num">2.40</td><td class="num">$525.00</td><td class="num amt"></td>
</tr>
<tr data-hours="1.1" data-rate="525">
<td>May 7</td><td><abbr title="Marcus Whitcombe">MW</abbr></td>
<td class="ledger__desc">Telephone conference with client re settlement posture and next steps.</td>
<td class="num">1.10</td><td class="num">$525.00</td><td class="num amt"></td>
</tr>
<tr data-hours="3.8" data-rate="340">
<td>May 12</td><td><abbr title="Priya Sundaram">PS</abbr></td>
<td class="ledger__desc">Draft demand letter; legal research on breach remedies and damages.</td>
<td class="num">3.80</td><td class="num">$340.00</td><td class="num amt"></td>
</tr>
<tr data-hours="2.0" data-rate="340">
<td>May 18</td><td><abbr title="Priya Sundaram">PS</abbr></td>
<td class="ledger__desc">Revise demand letter incorporating partner comments; finalize and serve.</td>
<td class="num">2.00</td><td class="num">$340.00</td><td class="num amt"></td>
</tr>
<tr data-hours="0.6" data-rate="195">
<td>May 24</td><td><abbr title="Jordan Beck, Paralegal">JB</abbr></td>
<td class="ledger__desc">Compile exhibits and assemble correspondence file for matter record.</td>
<td class="num">0.60</td><td class="num">$195.00</td><td class="num amt"></td>
</tr>
</tbody>
</table>
<!-- Expenses -->
<h2 class="block-h block-h--section" id="exp-h">Disbursements & expenses</h2>
<table class="ledger ledger--exp" aria-labelledby="exp-h">
<tbody id="expBody">
<tr data-amount="412">
<td>May 9</td><td colspan="4" class="ledger__desc">Court filing fee — Hennepin County District Court</td>
<td class="num amt"></td>
</tr>
<tr data-amount="68.5">
<td>May 18</td><td colspan="4" class="ledger__desc">Certified courier — demand letter delivery</td>
<td class="num amt"></td>
</tr>
</tbody>
</table>
</section>
<!-- Totals -->
<section class="summary" aria-label="Invoice totals">
<dl class="totals">
<div><dt>Fees subtotal</dt><dd id="feeSub">$0.00</dd></div>
<div><dt>Expenses</dt><dd id="expSub">$0.00</dd></div>
<div><dt>Tax (6.875%)</dt><dd id="taxAmt">$0.00</dd></div>
<div class="totals__paid" id="paidRow" hidden><dt>Payments received</dt><dd id="paidAmt">$0.00</dd></div>
<div class="totals__due"><dt>Total due</dt><dd id="totalDue">$0.00</dd></div>
</dl>
</section>
<hr class="rule" />
<footer class="foot">
<div class="foot__terms">
<p><strong>Payment terms:</strong> Net 30. Please reference invoice <strong>INV-2026-0488</strong>.</p>
<p class="foot__remit">Remit to: Whitcombe & Associates IOLTA · Routing 091000019 · Acct ••••4471</p>
</div>
<div class="foot__actions">
<button type="button" class="btn btn--ghost" id="printBtn">Print / PDF</button>
<button type="button" class="btn btn--pay" id="payBtn">Pay now</button>
</div>
</footer>
</main>
<script src="script.js"></script>
</body>
</html>Legal Invoice
A trust-first invoice for a law firm. The header carries the firm name, address and invoice meta (number, dates, matter); a client/matter block names the responsible attorney and the engagement. An itemized table lists billed time entries — date, attorney initials, narrative, hours and rate — each row totalling automatically, followed by a disbursements section for filing fees and courier costs.
Subtotal, expenses, tax and the total due recompute live from the rows, and a status badge flips between Unpaid, Partially paid and Paid. A Pay now button simulates a payment and settles the balance, while a print action triggers a clean, ink-friendly layout. The palette uses a deep navy, gold rules and warm-paper background with a serif headline face.
Built with semantic markup, real labels, focus styles and a responsive breakpoint. All interaction is vanilla JS — no libraries.
Illustrative UI only — not for real legal or accounting use.