Law Firm — Book a Consultation
A multi-step consultation booking flow for a law firm where a client picks a practice area, an optional attorney, a date from a month calendar and an available time slot, then enters contact details and a matter summary before a validated confirmation and success state.
MCP
Code
:root {
--navy: #1b2a4a;
--navy-d: #101b33;
--gold: #b08d57;
--gold-soft: #efe3cf;
--ink: #1a1f2b;
--muted: #5b6577;
--bg: #f5f3ee;
--white: #ffffff;
--line: #e4ddcf;
--ok: #2f7d54;
--err: #b4322c;
--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 12px 32px rgba(16, 27, 51, 0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--sans);
color: var(--ink);
background:
radial-gradient(1200px 480px at 50% -10%, rgba(176, 141, 87, 0.10), transparent 60%),
var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 40px 20px 80px;
}
/* Masthead */
.masthead { text-align: center; padding-bottom: 28px; }
.brand {
display: inline-flex;
align-items: center;
gap: 12px;
margin-bottom: 18px;
}
.brand__mark {
display: grid;
place-items: center;
width: 46px;
height: 46px;
border: 1.5px solid var(--gold);
border-radius: 8px;
font-family: var(--serif);
font-weight: 700;
color: var(--navy);
background: var(--white);
letter-spacing: 0.5px;
}
.brand__name {
font-family: var(--serif);
font-size: 1.35rem;
font-weight: 700;
color: var(--navy);
letter-spacing: 0.5px;
}
.brand__name span { color: var(--gold); font-weight: 400; }
.masthead__eyebrow {
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--gold);
font-weight: 600;
margin-bottom: 10px;
}
.masthead__title {
font-family: var(--serif);
font-size: 2.4rem;
color: var(--navy);
font-weight: 700;
line-height: 1.1;
}
.masthead__lead {
max-width: 520px;
margin: 12px auto 0;
color: var(--muted);
}
/* Layout */
.layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 24px;
align-items: start;
margin-top: 8px;
}
/* Cards */
.card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 22px 24px;
margin-bottom: 18px;
}
.card__head {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 18px;
padding-bottom: 14px;
border-bottom: 1px solid var(--line);
}
.card__head h2 {
font-family: var(--serif);
font-size: 1.18rem;
color: var(--navy);
font-weight: 700;
}
.card__head .opt { font-family: var(--sans); font-size: 0.78rem; color: var(--muted); font-weight: 400; }
.step {
display: grid;
place-items: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--navy);
color: var(--gold-soft);
font-size: 0.85rem;
font-weight: 700;
flex: none;
}
/* Practice area chips */
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.grid--attys { grid-template-columns: repeat(2, 1fr); }
.chip, .atty {
text-align: left;
font-family: inherit;
background: var(--white);
border: 1px solid var(--line);
border-radius: 8px;
padding: 12px 14px;
cursor: pointer;
transition: border-color 0.15s, box-shadow 0.15s, transform 0.08s, background 0.15s;
}
.chip:hover, .atty:hover { border-color: var(--gold); transform: translateY(-1px); }
.chip:focus-visible, .atty:focus-visible,
.cal__day:focus-visible, .slot:focus-visible,
input:focus-visible, textarea:focus-visible, .submit:focus-visible,
.cal__nav:focus-visible, .done__again:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
.chip__title { display: block; font-weight: 600; color: var(--ink); }
.chip__sub { display: block; font-size: 0.78rem; color: var(--muted); margin-top: 2px; }
.chip[aria-pressed="true"], .atty[aria-pressed="true"] {
border-color: var(--navy);
background: linear-gradient(0deg, var(--gold-soft), var(--gold-soft)) var(--white);
box-shadow: inset 0 0 0 1px var(--navy);
}
/* Attorneys */
.atty {
display: grid;
grid-template-columns: 40px 1fr;
grid-template-rows: auto auto;
column-gap: 12px;
align-items: center;
}
.atty__avatar {
grid-row: 1 / 3;
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--navy);
color: var(--gold-soft);
font-size: 0.82rem;
font-weight: 700;
}
.atty__name { font-weight: 600; }
.atty__role { font-size: 0.76rem; color: var(--muted); }
/* Calendar */
.cal__bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.cal__month {
font-family: var(--serif);
font-weight: 700;
color: var(--navy);
}
.cal__nav {
width: 34px;
height: 34px;
border: 1px solid var(--line);
background: var(--white);
border-radius: 8px;
font-size: 1.1rem;
color: var(--navy);
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.cal__nav:hover { border-color: var(--gold); background: var(--bg); }
.cal__dow {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 6px;
margin-bottom: 6px;
}
.cal__dow span {
text-align: center;
font-size: 0.68rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
}
.cal__grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 6px;
}
.cal__day {
aspect-ratio: 1 / 1;
border: 1px solid transparent;
border-radius: 8px;
background: var(--bg);
color: var(--ink);
font-family: inherit;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.08s;
}
.cal__day:hover:not(:disabled) { border-color: var(--gold); transform: translateY(-1px); }
.cal__day:disabled {
color: #b9bdc6;
background: transparent;
cursor: not-allowed;
}
.cal__day.is-empty { background: transparent; border: none; cursor: default; }
.cal__day[aria-selected="true"] {
background: var(--navy);
color: var(--white);
border-color: var(--navy);
}
/* Slots */
.slots { margin-top: 18px; padding-top: 16px; border-top: 1px dashed var(--line); }
.slots__title {
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
margin-bottom: 12px;
}
.slots__grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
.slot {
font-family: inherit;
border: 1px solid var(--line);
background: var(--white);
border-radius: 8px;
padding: 9px 4px;
font-size: 0.85rem;
font-weight: 600;
color: var(--ink);
cursor: pointer;
transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.08s;
}
.slot:hover:not(:disabled) { border-color: var(--gold); transform: translateY(-1px); }
.slot:disabled { color: #c1c5cd; background: var(--bg); cursor: not-allowed; text-decoration: line-through; }
.slot[aria-pressed="true"] { background: var(--gold); border-color: var(--gold); color: var(--navy-d); }
/* Fields */
.fields {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.field { display: flex; flex-direction: column; }
.field--full { grid-column: 1 / -1; }
.field label {
font-size: 0.8rem;
font-weight: 600;
color: var(--navy);
margin-bottom: 6px;
}
input[type="text"], input[type="email"], input[type="tel"], textarea {
font-family: inherit;
font-size: 0.95rem;
color: var(--ink);
background: var(--white);
border: 1px solid var(--line);
border-radius: 8px;
padding: 10px 12px;
transition: border-color 0.15s, box-shadow 0.15s;
}
input:focus, textarea:focus { border-color: var(--gold); }
input.invalid, textarea.invalid { border-color: var(--err); background: #fdf4f3; }
textarea { resize: vertical; }
.hint { font-size: 0.75rem; color: var(--muted); margin-top: 5px; text-align: right; }
.consent {
display: flex;
align-items: flex-start;
gap: 10px;
margin-top: 16px;
font-size: 0.85rem;
color: var(--muted);
cursor: pointer;
}
.consent input { margin-top: 3px; accent-color: var(--navy); flex: none; }
.error { color: var(--err); font-size: 0.78rem; margin-top: 6px; font-weight: 500; }
/* Submit */
.submit {
width: 100%;
font-family: inherit;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--white);
background: var(--navy);
border: 1px solid var(--navy-d);
border-radius: var(--radius);
padding: 15px;
cursor: pointer;
transition: background 0.15s, transform 0.08s, box-shadow 0.15s;
box-shadow: var(--shadow);
}
.submit:hover { background: var(--navy-d); transform: translateY(-1px); }
.submit:active { transform: translateY(0); }
/* Summary */
.summary { position: sticky; top: 20px; }
.summary__inner {
background: var(--navy);
color: var(--white);
border-radius: var(--radius);
padding: 24px;
box-shadow: var(--shadow);
border-top: 3px solid var(--gold);
}
.summary__title {
font-family: var(--serif);
font-size: 1.2rem;
margin-bottom: 18px;
color: var(--white);
}
.summary__list { display: grid; gap: 12px; }
.summary__row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
}
.summary__row dt {
font-size: 0.74rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--gold-soft);
opacity: 0.85;
}
.summary__row dd {
font-weight: 600;
text-align: right;
color: var(--white);
}
.summary__rule {
height: 1px;
background: rgba(239, 227, 207, 0.25);
margin: 18px 0;
}
.summary__note { font-size: 0.8rem; color: var(--gold-soft); opacity: 0.85; line-height: 1.5; }
/* Success */
.done {
position: fixed;
inset: 0;
background: rgba(16, 27, 51, 0.55);
display: grid;
place-items: center;
padding: 20px;
z-index: 20;
animation: fade 0.2s ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.done__card {
background: var(--white);
border-radius: 14px;
max-width: 420px;
width: 100%;
padding: 34px 30px;
text-align: center;
box-shadow: 0 24px 60px rgba(16, 27, 51, 0.35);
border-top: 4px solid var(--gold);
animation: pop 0.25s ease;
}
@keyframes pop { from { transform: translateY(10px) scale(0.98); opacity: 0; } to { transform: none; opacity: 1; } }
.done__check {
display: grid;
place-items: center;
width: 56px;
height: 56px;
margin: 0 auto 16px;
border-radius: 50%;
background: var(--ok);
color: #fff;
font-size: 1.6rem;
}
.done__title { font-family: var(--serif); font-size: 1.5rem; color: var(--navy); }
.done__lead { color: var(--muted); margin-top: 8px; }
.done__ref {
margin: 16px 0;
font-size: 0.9rem;
color: var(--muted);
}
.done__ref strong { color: var(--gold); font-family: var(--serif); letter-spacing: 0.04em; }
.done__list {
text-align: left;
display: grid;
gap: 8px;
background: var(--bg);
border: 1px solid var(--line);
border-radius: 8px;
padding: 14px 16px;
margin-bottom: 20px;
}
.done__list div { display: flex; justify-content: space-between; gap: 12px; font-size: 0.88rem; }
.done__list dt { color: var(--muted); }
.done__list dd { font-weight: 600; color: var(--ink); text-align: right; }
.done__again {
font-family: inherit;
font-weight: 700;
color: var(--navy);
background: var(--gold-soft);
border: 1px solid var(--gold);
border-radius: 8px;
padding: 11px 20px;
cursor: pointer;
transition: background 0.15s;
}
.done__again:hover { background: var(--gold); }
/* Responsive */
@media (max-width: 520px) {
.page { padding: 26px 14px 60px; }
.masthead__title { font-size: 1.9rem; }
.layout { grid-template-columns: 1fr; }
.grid, .grid--attys, .fields { grid-template-columns: 1fr; }
.slots__grid { grid-template-columns: repeat(3, 1fr); }
.summary { position: static; }
.card { padding: 18px 16px; }
.done__card { padding: 26px 20px; }
}(function () {
"use strict";
var state = {
area: null,
attorney: "No preference",
date: null, // Date object
time: null,
};
var monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December",
];
var today = new Date();
today.setHours(0, 0, 0, 0);
var view = new Date(today.getFullYear(), today.getMonth(), 1);
// A deterministic pool of times; some are "booked" per-day for realism.
var allTimes = ["9:00 AM", "10:00 AM", "11:00 AM", "1:00 PM", "2:00 PM", "3:00 PM", "4:00 PM", "5:00 PM"];
var $ = function (id) { return document.getElementById(id); };
/* ---------- Step 1 & 2: chips ---------- */
function wireRadioGroup(container, onPick) {
var buttons = container.querySelectorAll("button");
buttons.forEach(function (btn) {
btn.setAttribute("aria-pressed", "false");
btn.addEventListener("click", function () {
buttons.forEach(function (b) { b.setAttribute("aria-pressed", "false"); });
btn.setAttribute("aria-pressed", "true");
onPick(btn);
});
});
}
wireRadioGroup($("areas"), function (btn) {
state.area = btn.getAttribute("data-area").replace(/&/g, "&");
$("sumArea").textContent = state.area;
hide($("err-area"));
});
wireRadioGroup($("attorneys"), function (btn) {
state.attorney = btn.getAttribute("data-atty");
$("sumAtty").textContent = state.attorney;
});
/* ---------- Step 3: calendar ---------- */
function pad(n) { return n < 10 ? "0" + n : "" + n; }
function isoDay(d) { return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()); }
// pseudo-random but stable per day, used to mark some slots booked
function dayHash(d) {
var s = d.getFullYear() * 372 + (d.getMonth() + 1) * 31 + d.getDate();
return s;
}
function buildCalendar() {
$("calMonth").textContent = monthNames[view.getMonth()] + " " + view.getFullYear();
var grid = $("calGrid");
grid.innerHTML = "";
var first = new Date(view.getFullYear(), view.getMonth(), 1);
// Monday-first offset
var startOffset = (first.getDay() + 6) % 7;
var daysInMonth = new Date(view.getFullYear(), view.getMonth() + 1, 0).getDate();
for (var i = 0; i < startOffset; i++) {
var blank = document.createElement("div");
blank.className = "cal__day is-empty";
blank.setAttribute("aria-hidden", "true");
grid.appendChild(blank);
}
for (var day = 1; day <= daysInMonth; day++) {
var d = new Date(view.getFullYear(), view.getMonth(), day);
var btn = document.createElement("button");
btn.type = "button";
btn.className = "cal__day";
btn.textContent = day;
btn.setAttribute("role", "gridcell");
btn.setAttribute("aria-selected", "false");
var weekend = d.getDay() === 0 || d.getDay() === 6;
var past = d < today;
btn.setAttribute("aria-label", labelFor(d));
if (weekend || past) {
btn.disabled = true;
} else {
(function (dateObj) {
btn.addEventListener("click", function () {
selectDate(dateObj, btn);
});
})(d);
}
// keep current selection highlighted across re-renders
if (state.date && isoDay(state.date) === isoDay(d)) {
btn.setAttribute("aria-selected", "true");
}
grid.appendChild(btn);
}
}
function labelFor(d) {
return d.toLocaleDateString("en-US", {
weekday: "long", month: "long", day: "numeric", year: "numeric",
});
}
function selectDate(d, btn) {
state.date = d;
state.time = null;
// clear previous selection
var cells = $("calGrid").querySelectorAll(".cal__day");
cells.forEach(function (c) { c.setAttribute("aria-selected", "false"); });
btn.setAttribute("aria-selected", "true");
$("sumDate").textContent = labelFor(d);
$("sumTime").textContent = "—";
hide($("err-date"));
renderSlots(d);
}
function renderSlots(d) {
var wrap = $("slots");
var grid = $("slotGrid");
grid.innerHTML = "";
wrap.hidden = false;
var h = dayHash(d);
allTimes.forEach(function (t, idx) {
var btn = document.createElement("button");
btn.type = "button";
btn.className = "slot";
btn.textContent = t;
btn.setAttribute("aria-pressed", "false");
// mark ~2 slots booked deterministically
var booked = (h + idx * 7) % 5 === 0;
if (booked) {
btn.disabled = true;
btn.setAttribute("aria-label", t + " — unavailable");
} else {
btn.addEventListener("click", function () {
grid.querySelectorAll(".slot").forEach(function (s) { s.setAttribute("aria-pressed", "false"); });
btn.setAttribute("aria-pressed", "true");
state.time = t;
$("sumTime").textContent = t;
hide($("err-time"));
});
}
grid.appendChild(btn);
});
}
$("prevMonth").addEventListener("click", function () {
var candidate = new Date(view.getFullYear(), view.getMonth() - 1, 1);
// do not navigate before current month
var floor = new Date(today.getFullYear(), today.getMonth(), 1);
if (candidate < floor) return;
view = candidate;
buildCalendar();
});
$("nextMonth").addEventListener("click", function () {
view = new Date(view.getFullYear(), view.getMonth() + 1, 1);
buildCalendar();
});
/* ---------- Step 4: fields ---------- */
var matter = $("matter");
matter.addEventListener("input", function () {
$("count").textContent = matter.value.length;
});
function show(el) { if (el) el.hidden = false; }
function hide(el) { if (el) el.hidden = true; }
function validEmail(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim()); }
function validPhone(v) { return v.replace(/[^\d]/g, "").length >= 7; }
// clear field errors on input
["name", "email", "phone"].forEach(function (id) {
$(id).addEventListener("input", function () {
$(id).classList.remove("invalid");
hide($("err-" + id));
});
});
$("consent").addEventListener("change", function () { hide($("err-consent")); });
/* ---------- Submit ---------- */
$("booking").addEventListener("submit", function (e) {
e.preventDefault();
var ok = true;
var firstBad = null;
function fail(errEl, fieldEl) {
ok = false;
show(errEl);
if (fieldEl) fieldEl.classList.add("invalid");
if (!firstBad) firstBad = (fieldEl || errEl);
}
if (!state.area) fail($("err-area"));
if (!state.date) fail($("err-date"));
if (!state.time) fail($("err-time"));
var name = $("name"), email = $("email"), phone = $("phone");
if (!name.value.trim()) fail($("err-name"), name);
if (!validEmail(email.value)) fail($("err-email"), email);
if (!validPhone(phone.value)) fail($("err-phone"), phone);
if (!$("consent").checked) fail($("err-consent"));
if (!ok) {
if (firstBad && firstBad.scrollIntoView) {
firstBad.scrollIntoView({ behavior: "smooth", block: "center" });
if (firstBad.focus) firstBad.focus();
}
return;
}
succeed(name.value.trim());
});
function refNumber() {
var n = Math.floor(1000 + Math.random() * 9000);
var y = new Date().getFullYear();
return "WR-" + y + "-" + n;
}
function succeed(name) {
var ref = refNumber();
$("doneRef").textContent = ref;
var rows = [
["Client", name],
["Practice area", state.area],
["Attorney", state.attorney],
["Date", labelFor(state.date)],
["Time", state.time],
];
var list = $("doneDetails");
list.innerHTML = "";
rows.forEach(function (r) {
var div = document.createElement("div");
var dt = document.createElement("dt");
var dd = document.createElement("dd");
dt.textContent = r[0];
dd.textContent = r[1];
div.appendChild(dt);
div.appendChild(dd);
list.appendChild(div);
});
$("done").hidden = false;
}
$("again").addEventListener("click", function () {
$("done").hidden = true;
$("booking").reset();
state.area = null;
state.attorney = "No preference";
state.date = null;
state.time = null;
$("sumArea").textContent = "—";
$("sumAtty").textContent = "First available";
$("sumDate").textContent = "—";
$("sumTime").textContent = "—";
$("count").textContent = "0";
$("slots").hidden = true;
document.querySelectorAll('[aria-pressed="true"]').forEach(function (b) {
b.setAttribute("aria-pressed", "false");
});
document.querySelectorAll(".invalid").forEach(function (el) { el.classList.remove("invalid"); });
document.querySelectorAll(".error").forEach(function (el) { el.hidden = true; });
view = new Date(today.getFullYear(), today.getMonth(), 1);
buildCalendar();
window.scrollTo({ top: 0, behavior: "smooth" });
});
/* ---------- init ---------- */
buildCalendar();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Whitcombe & Reyes LLP — Book a Consultation</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page">
<header class="masthead">
<div class="brand">
<span class="brand__mark" aria-hidden="true">W&R</span>
<span class="brand__name">Whitcombe & Reyes <span>LLP</span></span>
</div>
<p class="masthead__eyebrow">Attorneys at Law · Est. 1986</p>
<h1 class="masthead__title">Book a Consultation</h1>
<p class="masthead__lead">
Tell us about your matter and choose a time. Initial consultations are
45 minutes and complimentary.
</p>
</header>
<div class="layout">
<form id="booking" class="form" novalidate>
<!-- Step 1: practice area -->
<section class="card" aria-labelledby="s1">
<div class="card__head">
<span class="step">1</span>
<h2 id="s1">Practice area</h2>
</div>
<div class="grid" id="areas" role="radiogroup" aria-label="Practice area">
<button type="button" class="chip" data-area="Family Law">
<span class="chip__title">Family Law</span>
<span class="chip__sub">Divorce, custody, support</span>
</button>
<button type="button" class="chip" data-area="Estate Planning">
<span class="chip__title">Estate Planning</span>
<span class="chip__sub">Wills, trusts, probate</span>
</button>
<button type="button" class="chip" data-area="Business & Corporate">
<span class="chip__title">Business & Corporate</span>
<span class="chip__sub">Formation, contracts</span>
</button>
<button type="button" class="chip" data-area="Real Estate">
<span class="chip__title">Real Estate</span>
<span class="chip__sub">Closings, disputes</span>
</button>
<button type="button" class="chip" data-area="Personal Injury">
<span class="chip__title">Personal Injury</span>
<span class="chip__sub">Claims, settlements</span>
</button>
<button type="button" class="chip" data-area="Immigration">
<span class="chip__title">Immigration</span>
<span class="chip__sub">Visas, citizenship</span>
</button>
</div>
<p class="error" id="err-area" hidden>Please choose a practice area.</p>
</section>
<!-- Step 2: attorney (optional) -->
<section class="card" aria-labelledby="s2">
<div class="card__head">
<span class="step">2</span>
<h2 id="s2">Attorney <span class="opt">(optional)</span></h2>
</div>
<div class="grid grid--attys" id="attorneys" role="radiogroup" aria-label="Attorney">
<button type="button" class="atty" data-atty="No preference">
<span class="atty__avatar" aria-hidden="true">—</span>
<span class="atty__name">No preference</span>
<span class="atty__role">First available</span>
</button>
<button type="button" class="atty" data-atty="Eleanor Whitcombe">
<span class="atty__avatar" aria-hidden="true">EW</span>
<span class="atty__name">Eleanor Whitcombe</span>
<span class="atty__role">Managing Partner</span>
</button>
<button type="button" class="atty" data-atty="Marcus Reyes">
<span class="atty__avatar" aria-hidden="true">MR</span>
<span class="atty__name">Marcus Reyes</span>
<span class="atty__role">Senior Partner</span>
</button>
<button type="button" class="atty" data-atty="Priya Nandakumar">
<span class="atty__avatar" aria-hidden="true">PN</span>
<span class="atty__name">Priya Nandakumar</span>
<span class="atty__role">Associate</span>
</button>
</div>
</section>
<!-- Step 3: date & time -->
<section class="card" aria-labelledby="s3">
<div class="card__head">
<span class="step">3</span>
<h2 id="s3">Date & time</h2>
</div>
<div class="cal">
<div class="cal__bar">
<button type="button" class="cal__nav" id="prevMonth" aria-label="Previous month">‹</button>
<span class="cal__month" id="calMonth" aria-live="polite">Month</span>
<button type="button" class="cal__nav" id="nextMonth" aria-label="Next month">›</button>
</div>
<div class="cal__dow" aria-hidden="true">
<span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span>
</div>
<div class="cal__grid" id="calGrid" role="grid" aria-label="Choose a date"></div>
</div>
<p class="error" id="err-date" hidden>Please choose an available date.</p>
<div class="slots" id="slots" hidden>
<h3 class="slots__title">Available times</h3>
<div class="slots__grid" id="slotGrid" role="radiogroup" aria-label="Time slot"></div>
<p class="error" id="err-time" hidden>Please choose a time.</p>
</div>
</section>
<!-- Step 4: contact -->
<section class="card" aria-labelledby="s4">
<div class="card__head">
<span class="step">4</span>
<h2 id="s4">Your details</h2>
</div>
<div class="fields">
<div class="field">
<label for="name">Full name</label>
<input id="name" name="name" type="text" autocomplete="name" placeholder="Jordan Avery" required />
<p class="error" id="err-name" hidden>Please enter your name.</p>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required />
<p class="error" id="err-email" hidden>Please enter a valid email.</p>
</div>
<div class="field">
<label for="phone">Phone</label>
<input id="phone" name="phone" type="tel" autocomplete="tel" placeholder="(415) 555-0182" required />
<p class="error" id="err-phone" hidden>Please enter a phone number.</p>
</div>
<div class="field field--full">
<label for="matter">Brief description of your matter</label>
<textarea id="matter" name="matter" rows="4" maxlength="600"
placeholder="A short summary helps us assign the right attorney. Do not include sensitive details."></textarea>
<p class="hint"><span id="count">0</span> / 600</p>
</div>
</div>
<label class="consent">
<input type="checkbox" id="consent" required />
<span>I understand this request does not create an attorney–client relationship.</span>
</label>
<p class="error" id="err-consent" hidden>Please acknowledge to continue.</p>
</section>
<button type="submit" class="submit" id="submit">Request consultation</button>
</form>
<!-- Live summary -->
<aside class="summary" aria-label="Your consultation summary">
<div class="summary__inner">
<h2 class="summary__title">Your consultation</h2>
<dl class="summary__list">
<div class="summary__row"><dt>Practice area</dt><dd id="sumArea">—</dd></div>
<div class="summary__row"><dt>Attorney</dt><dd id="sumAtty">First available</dd></div>
<div class="summary__row"><dt>Date</dt><dd id="sumDate">—</dd></div>
<div class="summary__row"><dt>Time</dt><dd id="sumTime">—</dd></div>
<div class="summary__row"><dt>Duration</dt><dd>45 minutes</dd></div>
</dl>
<div class="summary__rule" aria-hidden="true"></div>
<p class="summary__note">No charge for the initial consultation. You will receive a
confirmation email within one business day.</p>
</div>
</aside>
</div>
<!-- Success state -->
<div class="done" id="done" hidden role="status">
<div class="done__card">
<span class="done__check" aria-hidden="true">✓</span>
<h2 class="done__title">Consultation requested</h2>
<p class="done__lead">Thank you. A member of our team will confirm shortly.</p>
<p class="done__ref">Reference <strong id="doneRef">—</strong></p>
<dl class="done__list" id="doneDetails"></dl>
<button type="button" class="done__again" id="again">Book another</button>
</div>
</div>
</main>
<script src="script.js"></script>
</body>
</html>Book a Consultation
An authoritative, trust-first booking page for a law firm. The client chooses a practice area, optionally requests a specific attorney, then picks a day from a simple month calendar and an available time slot. A contact form collects name, email, phone and a short description of the matter, with live inline validation.
A live summary panel mirrors every choice as it is made, so the client always sees what they are about to book. On submit, the form validates again and reveals a success state with a generated reference number and the booked details.
The whole flow is vanilla HTML, CSS and JavaScript — no libraries, no calendar dependency. The
calendar is generated from the current month, disables past days and weekends, and keeps keyboard
focus and aria state in sync.
Illustrative UI only — not legal advice and not for real client intake.