Law Firm — Client Intake Form
A four-step law-firm client intake wizard with a progress indicator, per-step validation, conditional fields for the matter type, an automated conflict-of-interest check, and a final review summary that persists every entered value across steps before a confirming submit.
MCP
Code
:root {
--navy: #1b2a4a;
--navy-d: #101b33;
--gold: #b08d57;
--gold-soft: #efe3cf;
--ink: #1a1f2b;
--muted: #5b6577;
--bg: #f5f3ee;
--white: #ffffff;
--line: #e4ded2;
--danger: #a4341f;
--ok: #2f6b46;
--serif: "Georgia", "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--radius: 10px;
--shadow: 0 18px 50px -28px rgba(16, 27, 51, 0.5);
}
* {
box-sizing: border-box;
margin: 0;
}
body {
font-family: var(--sans);
color: var(--ink);
background: var(--bg);
line-height: 1.55;
padding: 40px 20px;
-webkit-font-smoothing: antialiased;
}
.intake {
max-width: 720px;
margin: 0 auto;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 36px 38px 30px;
}
.intake__head {
text-align: center;
padding-bottom: 22px;
border-bottom: 1px solid var(--gold);
margin-bottom: 26px;
}
.intake__firm {
font-family: var(--serif);
letter-spacing: 0.16em;
text-transform: uppercase;
font-size: 0.72rem;
color: var(--gold);
font-weight: 600;
}
.intake__head h1 {
font-family: var(--serif);
color: var(--navy);
font-size: 1.9rem;
margin: 6px 0 4px;
font-weight: 700;
}
.intake__sub {
color: var(--muted);
font-size: 0.9rem;
}
/* Steps */
.steps {
list-style: none;
display: flex;
justify-content: space-between;
padding: 0;
margin: 0 0 14px;
gap: 6px;
}
.steps__item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
text-align: center;
}
.steps__dot {
width: 30px;
height: 30px;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 600;
font-size: 0.85rem;
background: var(--bg);
color: var(--muted);
border: 1.5px solid var(--line);
transition: all 0.2s ease;
}
.steps__label {
font-size: 0.74rem;
color: var(--muted);
font-weight: 500;
}
.steps__item.is-active .steps__dot {
background: var(--navy);
border-color: var(--navy);
color: var(--white);
}
.steps__item.is-active .steps__label {
color: var(--navy);
font-weight: 600;
}
.steps__item.is-done .steps__dot {
background: var(--gold);
border-color: var(--gold);
color: var(--white);
}
.steps__item.is-done .steps__dot::after {
content: "✓";
}
.steps__item.is-done .steps__dot {
font-size: 0;
}
.steps__item.is-done .steps__dot::after {
font-size: 0.85rem;
}
/* Progress bar */
.progress {
height: 4px;
background: var(--line);
border-radius: 4px;
overflow: hidden;
margin-bottom: 28px;
}
.progress__fill {
display: block;
height: 100%;
background: linear-gradient(90deg, var(--gold), var(--navy));
transition: width 0.35s ease;
}
/* Panels */
.panel__title {
font-family: var(--serif);
color: var(--navy);
font-size: 1.3rem;
margin-bottom: 6px;
}
.panel__lead,
.panel__title + .grid {
margin-top: 0;
}
.panel__lead {
color: var(--muted);
font-size: 0.9rem;
margin-bottom: 18px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px 18px;
margin-top: 16px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.field--full {
grid-column: 1 / -1;
}
label {
font-size: 0.83rem;
font-weight: 600;
color: var(--ink);
}
.req {
color: var(--gold);
}
input,
select,
textarea {
font-family: var(--sans);
font-size: 0.94rem;
color: var(--ink);
background: var(--bg);
border: 1.5px solid var(--line);
border-radius: 8px;
padding: 10px 12px;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
textarea {
resize: vertical;
}
input:focus,
select:focus,
textarea:focus {
outline: none;
border-color: var(--gold);
background: var(--white);
box-shadow: 0 0 0 3px var(--gold-soft);
}
input.is-invalid,
select.is-invalid,
textarea.is-invalid {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(164, 52, 31, 0.12);
}
.hint {
font-size: 0.76rem;
color: var(--muted);
}
.error {
font-size: 0.76rem;
color: var(--danger);
font-weight: 500;
min-height: 0;
}
.error:empty {
display: none;
}
.check {
display: flex;
align-items: flex-start;
gap: 10px;
font-weight: 500;
font-size: 0.88rem;
color: var(--ink);
cursor: pointer;
}
.check input {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: var(--navy);
flex: none;
padding: 0;
}
/* Conflict result */
.conflict {
margin-top: 12px;
font-size: 0.86rem;
font-weight: 500;
}
.conflict__box {
border-radius: 8px;
padding: 12px 14px;
border: 1.5px solid;
display: flex;
gap: 10px;
align-items: flex-start;
}
.conflict__box .badge {
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 2px 8px;
border-radius: 999px;
flex: none;
}
.conflict--clear .conflict__box {
border-color: rgba(47, 107, 70, 0.4);
background: rgba(47, 107, 70, 0.08);
color: var(--ok);
}
.conflict--clear .badge {
background: var(--ok);
color: var(--white);
}
.conflict--flag .conflict__box {
border-color: rgba(164, 52, 31, 0.4);
background: rgba(164, 52, 31, 0.08);
color: var(--danger);
}
.conflict--flag .badge {
background: var(--danger);
color: var(--white);
}
/* Review */
.review {
margin: 6px 0 18px;
border: 1px solid var(--line);
border-radius: var(--radius);
overflow: hidden;
}
.review__row {
display: grid;
grid-template-columns: 180px 1fr;
gap: 14px;
padding: 11px 16px;
border-bottom: 1px solid var(--line);
}
.review__row:last-child {
border-bottom: none;
}
.review__row:nth-child(odd) {
background: var(--bg);
}
.review dt {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--muted);
font-weight: 600;
}
.review dd {
font-size: 0.9rem;
color: var(--ink);
word-break: break-word;
}
.review dd.empty {
color: var(--muted);
font-style: italic;
}
.disclaimer {
font-size: 0.8rem;
color: var(--muted);
background: var(--gold-soft);
border-left: 3px solid var(--gold);
padding: 12px 14px;
border-radius: 0 6px 6px 0;
}
/* Nav */
.nav {
display: flex;
align-items: center;
gap: 12px;
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid var(--line);
}
.nav__spacer {
flex: 1;
}
.btn {
font-family: var(--sans);
font-size: 0.9rem;
font-weight: 600;
padding: 11px 22px;
border-radius: 8px;
border: 1.5px solid transparent;
cursor: pointer;
transition: transform 0.12s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--gold-soft);
}
.btn--primary {
background: var(--navy);
color: var(--white);
}
.btn--primary:hover {
background: var(--navy-d);
}
.btn--ghost {
background: var(--white);
color: var(--navy);
border-color: var(--line);
}
.btn--ghost:hover {
border-color: var(--gold);
color: var(--gold);
}
/* Done */
.done {
text-align: center;
padding: 24px 0 8px;
}
.done__seal {
display: inline-grid;
place-items: center;
width: 64px;
height: 64px;
border-radius: 50%;
background: var(--gold);
color: var(--white);
font-size: 1.8rem;
margin-bottom: 14px;
}
.done h2 {
font-family: var(--serif);
color: var(--navy);
margin-bottom: 6px;
}
.done p {
color: var(--muted);
}
[hidden] {
display: none !important;
}
@media (max-width: 520px) {
body {
padding: 20px 12px;
}
.intake {
padding: 24px 20px 22px;
}
.intake__head h1 {
font-size: 1.5rem;
}
.grid {
grid-template-columns: 1fr;
}
.steps__label {
display: none;
}
.review__row {
grid-template-columns: 1fr;
gap: 4px;
}
.nav {
flex-wrap: wrap;
}
}(function () {
"use strict";
var form = document.getElementById("intakeForm");
if (!form) return;
var panels = Array.prototype.slice.call(form.querySelectorAll(".panel"));
var stepItems = Array.prototype.slice.call(document.querySelectorAll(".steps__item"));
var progressBar = document.querySelector(".progress");
var progressFill = document.querySelector(".progress__fill");
var backBtn = document.getElementById("backBtn");
var nextBtn = document.getElementById("nextBtn");
var submitBtn = document.getElementById("submitBtn");
var litigationBlock = document.getElementById("litigationBlock");
var matterSelect = document.getElementById("matterType");
var runConflictBtn = document.getElementById("runConflict");
var conflictResult = document.getElementById("conflictResult");
var reviewList = document.getElementById("reviewList");
var doneEl = document.getElementById("done");
var current = 0;
var TOTAL = panels.length;
// Persisted state across steps.
var state = {
fullName: "", email: "", phone: "", contactMethod: "Email",
matterType: "", urgency: "Standard", opposingParty: "", summary: "",
relatedParties: "", priorRep: false, attest: false
};
// Mock existing-client roster for the conflict screen.
var FIRM_CLIENTS = [
"harlow ventures", "dennis park", "northgate realty",
"claudia messina", "orion freight co", "samuel okafor"
];
var LABELS = {
fullName: "Full legal name", email: "Email", phone: "Phone",
contactMethod: "Preferred contact", matterType: "Matter type",
urgency: "Urgency", opposingParty: "Opposing party", summary: "Description",
relatedParties: "Other parties", priorRep: "Prior representation",
attest: "Attestation"
};
var REVIEW_ORDER = [
"fullName", "email", "phone", "contactMethod", "matterType",
"urgency", "opposingParty", "summary", "relatedParties", "priorRep"
];
function isLitigation() {
return state.matterType === "Civil litigation" || state.matterType === "Personal injury";
}
// ---- State sync ----
function readField(id) {
var el = document.getElementById(id);
if (!el) return;
if (el.type === "checkbox") state[id] = el.checked;
else state[id] = el.value.trim();
}
function readPanel(index) {
var keysByPanel = [
["fullName", "email", "phone", "contactMethod"],
["matterType", "urgency", "opposingParty", "summary"],
["relatedParties", "priorRep", "attest"]
];
var keys = keysByPanel[index];
if (keys) keys.forEach(readField);
}
function restoreFields() {
Object.keys(state).forEach(function (id) {
var el = document.getElementById(id);
if (!el) return;
if (el.type === "checkbox") el.checked = !!state[id];
else el.value = state[id];
});
}
// ---- Errors ----
function setError(id, msg) {
var el = document.getElementById(id);
var slot = form.querySelector('.error[data-for="' + id + '"]');
if (el) el.classList.toggle("is-invalid", !!msg);
if (slot) slot.textContent = msg || "";
}
function clearErrors() {
form.querySelectorAll(".error").forEach(function (e) { e.textContent = ""; });
form.querySelectorAll(".is-invalid").forEach(function (e) { e.classList.remove("is-invalid"); });
}
function emailOk(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v); }
function phoneOk(v) { return (v.replace(/\D/g, "").length >= 10); }
// ---- Validation per step ----
function validate(index) {
var ok = true;
var firstBad = null;
function fail(id, msg) {
setError(id, msg);
ok = false;
if (!firstBad) firstBad = document.getElementById(id);
}
if (index === 0) {
if (!state.fullName) fail("fullName", "Please enter your full legal name.");
if (!state.email) fail("email", "Email is required.");
else if (!emailOk(state.email)) fail("email", "Enter a valid email address.");
if (!state.phone) fail("phone", "Phone number is required.");
else if (!phoneOk(state.phone)) fail("phone", "Enter a valid phone number.");
} else if (index === 1) {
if (!state.matterType) fail("matterType", "Select a practice area.");
if (isLitigation() && !state.opposingParty) {
fail("opposingParty", "Opposing party is required for litigation matters.");
}
if (!state.summary) fail("summary", "A brief description is required.");
else if (state.summary.length < 15) fail("summary", "Please add a little more detail (15+ characters).");
} else if (index === 2) {
if (!state.attest) fail("attest", "You must attest before continuing.");
}
if (firstBad && firstBad.focus) firstBad.focus();
return ok;
}
// ---- Conditional fields ----
function syncConditional() {
if (litigationBlock) litigationBlock.hidden = !isLitigation();
}
if (matterSelect) {
matterSelect.addEventListener("change", function () {
state.matterType = matterSelect.value;
syncConditional();
});
}
// ---- Conflict screen ----
function runConflictScreen() {
readPanel(2);
var names = state.relatedParties.split(",")
.map(function (n) { return n.trim().toLowerCase(); })
.filter(Boolean);
if (isLitigation() && state.opposingParty) {
names.push(state.opposingParty.toLowerCase());
}
var hits = names.filter(function (n) {
return FIRM_CLIENTS.indexOf(n) !== -1;
});
conflictResult.className = "conflict";
if (hits.length) {
conflictResult.classList.add("conflict--flag");
conflictResult.innerHTML =
'<div class="conflict__box"><span class="badge">Conflict</span>' +
"<span>Potential conflict found involving <strong>" +
hits.map(titleCase).join(", ") +
"</strong>. Our intake team will review before any engagement.</span></div>";
} else {
conflictResult.classList.add("conflict--clear");
conflictResult.innerHTML =
'<div class="conflict__box"><span class="badge">Clear</span>' +
"<span>No conflicts detected against our current client roster.</span></div>";
}
}
function titleCase(s) {
return s.replace(/\b\w/g, function (c) { return c.toUpperCase(); });
}
if (runConflictBtn) runConflictBtn.addEventListener("click", runConflictScreen);
// ---- Review ----
function buildReview() {
reviewList.innerHTML = "";
REVIEW_ORDER.forEach(function (key) {
if (key === "opposingParty" && !isLitigation()) return;
var row = document.createElement("div");
row.className = "review__row";
var dt = document.createElement("dt");
dt.textContent = LABELS[key];
var dd = document.createElement("dd");
var val = state[key];
if (key === "priorRep") val = state.priorRep ? "Yes" : "No";
if (!val && val !== "No") {
dd.textContent = "Not provided";
dd.className = "empty";
} else {
dd.textContent = val;
}
row.appendChild(dt);
row.appendChild(dd);
reviewList.appendChild(row);
});
}
// ---- Navigation / render ----
function render() {
panels.forEach(function (p, i) {
var on = i === current;
p.hidden = !on;
p.classList.toggle("is-current", on);
});
stepItems.forEach(function (item, i) {
item.classList.toggle("is-active", i === current);
item.classList.toggle("is-done", i < current);
});
var pct = ((current + 1) / TOTAL) * 100;
progressFill.style.width = pct + "%";
progressBar.setAttribute("aria-valuenow", String(current + 1));
backBtn.hidden = current === 0;
nextBtn.hidden = current === TOTAL - 1;
submitBtn.hidden = current !== TOTAL - 1;
if (current === TOTAL - 1) buildReview();
var heading = panels[current].querySelector(".panel__title");
if (heading) heading.setAttribute("tabindex", "-1");
if (heading && heading.focus) heading.focus();
}
function goNext() {
readPanel(current);
clearErrors();
if (!validate(current)) return;
syncConditional();
if (current < TOTAL - 1) {
current++;
render();
}
}
function goBack() {
readPanel(current);
clearErrors();
if (current > 0) {
current--;
render();
}
}
nextBtn.addEventListener("click", goNext);
backBtn.addEventListener("click", goBack);
form.addEventListener("submit", function (e) {
e.preventDefault();
readPanel(current);
clearErrors();
if (!validate(2)) {
// Make sure attestation passed (it lives on step 3).
current = 2;
render();
validate(2);
return;
}
form.hidden = true;
document.querySelector(".steps").hidden = true;
progressBar.hidden = true;
doneEl.hidden = false;
doneEl.focus && doneEl.focus();
});
// Live-clear an error as the user fixes a field.
form.addEventListener("input", function (e) {
var t = e.target;
if (t && t.id && t.classList.contains("is-invalid")) {
setError(t.id, "");
}
});
// Init
restoreFields();
syncConditional();
render();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Law Firm — Client Intake Form</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>
<main class="intake" aria-labelledby="intake-title">
<header class="intake__head">
<p class="intake__firm">Whitfield & Crane LLP</p>
<h1 id="intake-title">New Client Intake</h1>
<p class="intake__sub">Confidential — complete all four steps to request a consultation.</p>
</header>
<!-- Progress indicator -->
<ol class="steps" aria-label="Intake progress">
<li class="steps__item is-active" data-step="0">
<span class="steps__dot">1</span><span class="steps__label">Your info</span>
</li>
<li class="steps__item" data-step="1">
<span class="steps__dot">2</span><span class="steps__label">Matter details</span>
</li>
<li class="steps__item" data-step="2">
<span class="steps__dot">3</span><span class="steps__label">Conflict check</span>
</li>
<li class="steps__item" data-step="3">
<span class="steps__dot">4</span><span class="steps__label">Review & submit</span>
</li>
</ol>
<div class="progress" role="progressbar" aria-valuemin="1" aria-valuemax="4" aria-valuenow="1">
<span class="progress__fill" style="width:25%"></span>
</div>
<form id="intakeForm" novalidate>
<!-- Step 1: Your info -->
<section class="panel is-current" data-panel="0" aria-label="Your info">
<h2 class="panel__title">Your information</h2>
<div class="grid">
<div class="field">
<label for="fullName">Full legal name <span class="req">*</span></label>
<input id="fullName" name="fullName" type="text" autocomplete="name" placeholder="e.g. Margaret A. Donnelly" />
<p class="error" data-for="fullName"></p>
</div>
<div class="field">
<label for="email">Email <span class="req">*</span></label>
<input id="email" name="email" type="email" autocomplete="email" placeholder="[email protected]" />
<p class="error" data-for="email"></p>
</div>
<div class="field">
<label for="phone">Phone <span class="req">*</span></label>
<input id="phone" name="phone" type="tel" autocomplete="tel" placeholder="(415) 555-0182" />
<p class="error" data-for="phone"></p>
</div>
<div class="field">
<label for="contactMethod">Preferred contact</label>
<select id="contactMethod" name="contactMethod">
<option value="Email">Email</option>
<option value="Phone call">Phone call</option>
<option value="Text message">Text message</option>
</select>
</div>
</div>
</section>
<!-- Step 2: Matter details -->
<section class="panel" data-panel="1" aria-label="Matter details" hidden>
<h2 class="panel__title">Matter details</h2>
<div class="grid">
<div class="field">
<label for="matterType">Matter type <span class="req">*</span></label>
<select id="matterType" name="matterType">
<option value="">Select a practice area…</option>
<option value="Family law">Family law</option>
<option value="Estate planning">Estate planning</option>
<option value="Business / contracts">Business / contracts</option>
<option value="Real estate">Real estate</option>
<option value="Civil litigation">Civil litigation</option>
<option value="Personal injury">Personal injury</option>
</select>
<p class="error" data-for="matterType"></p>
</div>
<div class="field">
<label for="urgency">Urgency</label>
<select id="urgency" name="urgency">
<option value="Standard">Standard — within 2 weeks</option>
<option value="Soon">Soon — within a few days</option>
<option value="Urgent">Urgent — a deadline is near</option>
</select>
</div>
<div class="field field--full" id="litigationBlock" hidden>
<label for="opposingParty">Opposing party name <span class="req">*</span></label>
<input id="opposingParty" name="opposingParty" type="text" placeholder="Name of the other party in the dispute" />
<p class="hint">Required for litigation matters so we can screen for conflicts.</p>
<p class="error" data-for="opposingParty"></p>
</div>
<div class="field field--full">
<label for="summary">Brief description <span class="req">*</span></label>
<textarea id="summary" name="summary" rows="4" placeholder="Tell us what happened and what outcome you are hoping for."></textarea>
<p class="error" data-for="summary"></p>
</div>
</div>
</section>
<!-- Step 3: Conflict check -->
<section class="panel" data-panel="2" aria-label="Conflict check" hidden>
<h2 class="panel__title">Conflict of interest check</h2>
<p class="panel__lead">
We screen every prospective matter against our current and former clients. List anyone else
involved so we can confirm we are free to represent you.
</p>
<div class="grid">
<div class="field field--full">
<label for="relatedParties">Other parties involved</label>
<input id="relatedParties" name="relatedParties" type="text" placeholder="e.g. Harlow Ventures, Dennis Park" />
<p class="hint">Separate multiple names with commas.</p>
</div>
<div class="field field--full">
<label class="check">
<input id="priorRep" name="priorRep" type="checkbox" />
<span>I have previously been represented by this firm.</span>
</label>
</div>
<div class="field field--full">
<button type="button" id="runConflict" class="btn btn--ghost">Run conflict screen</button>
<div id="conflictResult" class="conflict" role="status" aria-live="polite"></div>
</div>
<div class="field field--full">
<label class="check">
<input id="attest" name="attest" type="checkbox" />
<span>I attest the information above is accurate to the best of my knowledge. <span class="req">*</span></span>
</label>
<p class="error" data-for="attest"></p>
</div>
</div>
</section>
<!-- Step 4: Review & submit -->
<section class="panel" data-panel="3" aria-label="Review and submit" hidden>
<h2 class="panel__title">Review & submit</h2>
<p class="panel__lead">Please confirm the details below before submitting your request.</p>
<dl class="review" id="reviewList"></dl>
<p class="disclaimer">
Submitting this form does not create an attorney–client relationship. A member of our
intake team will follow up within one business day.
</p>
</section>
<!-- Nav -->
<div class="nav">
<button type="button" id="backBtn" class="btn btn--ghost" hidden>Back</button>
<div class="nav__spacer"></div>
<button type="button" id="nextBtn" class="btn btn--primary">Continue</button>
<button type="submit" id="submitBtn" class="btn btn--primary" hidden>Submit request</button>
</div>
</form>
<div id="done" class="done" hidden role="status" aria-live="polite">
<span class="done__seal">✓</span>
<h2>Request received</h2>
<p>Thank you. Our intake team will contact you within one business day.</p>
</div>
</main>
<script src="script.js"></script>
</body>
</html>Client Intake Form
An authoritative, trust-first intake wizard for a law firm. Prospective clients move through four clearly numbered steps — Your info, Matter details, Conflict check and Review & submit — guided by a gold-accented progress bar that marks completed steps with a check.
Each step validates before it lets you advance: required fields, email and phone formats, and a conditional opposing-party block that only appears for litigation matters. The conflict-check step runs a small in-browser screen against a list of existing firm clients and flashes a clear notice when a potential conflict is detected. The final step renders a read-only review summary built from everything entered, and the whole form persists its values as you step back and forth.
Everything is vanilla JS with no libraries — state lives in a single object, fields restore on navigation, and focus moves to the first invalid control on a failed validation.
Illustrative UI only — not legal advice and not for real client intake.