Insurance — Get a Quote Form
A compact lead-capture quote form for an insurance site with a coverage-type select, smart inputs, contact fields, inline validation and a privacy note, that on submit reveals a friendly success state showing an estimated monthly premium range.
MCP
Code
:root {
--blue: #2563eb;
--blue-d: #1d4ed8;
--blue-50: #eef4ff;
--green: #15a06b;
--ink: #0f1a2b;
--muted: #5a6b82;
--bg: #f3f7fc;
--white: #ffffff;
--line: #e2eaf4;
--danger: #d4344a;
--radius: 16px;
--shadow: 0 10px 30px rgba(15, 26, 43, 0.08);
--font: "Inter", system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: var(--font);
background:
radial-gradient(900px 500px at 100% -10%, var(--blue-50), transparent 60%),
var(--bg);
color: var(--ink);
-webkit-font-smoothing: antialiased;
display: flex;
justify-content: center;
padding: 32px 16px;
min-height: 100vh;
}
.quote {
width: 100%;
max-width: 440px;
}
.card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 28px;
}
.card__head {
margin-bottom: 22px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 14px;
}
.brand__mark {
width: 26px;
height: 26px;
fill: var(--blue-50);
stroke: var(--blue);
stroke-width: 1.6;
stroke-linecap: round;
stroke-linejoin: round;
}
.brand__check {
fill: none;
stroke: var(--green);
stroke-width: 2;
}
.brand__name {
font-weight: 700;
font-size: 0.9rem;
letter-spacing: -0.01em;
}
h1 {
font-size: 1.55rem;
line-height: 1.15;
letter-spacing: -0.02em;
margin: 0 0 6px;
}
.card__sub {
margin: 0 0 14px;
color: var(--muted);
font-size: 0.92rem;
}
.proof {
display: flex;
flex-wrap: wrap;
gap: 8px;
list-style: none;
margin: 0;
padding: 0;
}
.proof li {
background: var(--blue-50);
color: var(--blue-d);
font-size: 0.74rem;
font-weight: 600;
padding: 5px 10px;
border-radius: 999px;
}
.proof strong {
font-weight: 800;
}
.field {
margin-bottom: 16px;
}
.row {
display: flex;
gap: 12px;
}
.row .field {
flex: 1;
}
label {
display: block;
font-size: 0.82rem;
font-weight: 600;
margin-bottom: 6px;
color: var(--ink);
}
input,
select {
width: 100%;
font-family: inherit;
font-size: 0.95rem;
color: var(--ink);
background: var(--white);
border: 1.5px solid var(--line);
border-radius: 11px;
padding: 11px 13px;
transition: border-color 0.15s, box-shadow 0.15s;
}
select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235a6b82' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 13px center;
background-size: 16px;
padding-right: 38px;
cursor: pointer;
}
input::placeholder {
color: #9fb0c6;
}
input:focus,
select:focus {
outline: none;
border-color: var(--blue);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18);
}
.field.invalid input,
.field.invalid select {
border-color: var(--danger);
}
.field.invalid input:focus,
.field.invalid select:focus {
box-shadow: 0 0 0 3px rgba(212, 52, 74, 0.18);
}
.amount {
position: relative;
}
.amount__sym {
position: absolute;
left: 13px;
top: 50%;
transform: translateY(-50%);
color: var(--muted);
font-weight: 600;
pointer-events: none;
}
.amount input {
padding-left: 26px;
}
.hint {
margin: 6px 0 0;
font-size: 0.78rem;
color: var(--muted);
}
.error {
margin: 6px 0 0;
font-size: 0.78rem;
font-weight: 600;
color: var(--danger);
}
.rule {
border: none;
border-top: 1px solid var(--line);
margin: 20px 0;
}
.submit {
width: 100%;
font-family: inherit;
font-size: 1rem;
font-weight: 700;
color: var(--white);
background: var(--blue);
border: none;
border-radius: 12px;
padding: 14px;
cursor: pointer;
transition: background 0.15s, transform 0.05s;
}
.submit:hover {
background: var(--blue-d);
}
.submit:active {
transform: translateY(1px);
}
.submit:focus-visible {
outline: 3px solid rgba(37, 99, 235, 0.4);
outline-offset: 2px;
}
.privacy {
display: flex;
align-items: flex-start;
gap: 8px;
margin: 16px 0 0;
font-size: 0.78rem;
color: var(--muted);
line-height: 1.5;
}
.lock {
flex: none;
width: 16px;
height: 16px;
margin-top: 1px;
fill: none;
stroke: var(--green);
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.privacy a {
color: var(--blue-d);
font-weight: 600;
}
/* Success state */
.card--done {
text-align: center;
animation: rise 0.35s ease both;
}
@keyframes rise {
from {
opacity: 0;
transform: translateY(10px);
}
}
.done__icon {
width: 56px;
height: 56px;
margin: 0 auto 14px;
border-radius: 50%;
background: rgba(21, 160, 107, 0.12);
display: grid;
place-items: center;
}
.done__icon svg {
width: 30px;
height: 30px;
fill: none;
stroke: var(--green);
stroke-width: 2.6;
stroke-linecap: round;
stroke-linejoin: round;
}
.card--done h2 {
font-size: 1.35rem;
letter-spacing: -0.02em;
margin: 0 0 4px;
}
.done__sub {
margin: 0 0 18px;
color: var(--muted);
font-size: 0.92rem;
}
.estimate {
display: flex;
align-items: baseline;
justify-content: center;
gap: 6px;
background: var(--blue-50);
border-radius: 14px;
padding: 20px 12px;
margin-bottom: 18px;
flex-wrap: wrap;
}
.estimate__from,
.estimate__to {
font-size: 2.1rem;
font-weight: 800;
letter-spacing: -0.03em;
color: var(--blue-d);
}
.estimate__sep {
font-size: 1.6rem;
color: var(--blue);
font-weight: 700;
}
.estimate__per {
font-size: 0.9rem;
font-weight: 600;
color: var(--muted);
}
.done__note {
font-size: 0.86rem;
color: var(--muted);
line-height: 1.55;
margin: 0 0 20px;
}
.done__note strong {
color: var(--ink);
}
.ghost {
font-family: inherit;
font-size: 0.92rem;
font-weight: 600;
color: var(--blue-d);
background: var(--white);
border: 1.5px solid var(--line);
border-radius: 11px;
padding: 11px 18px;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.ghost:hover {
border-color: var(--blue);
background: var(--blue-50);
}
@media (max-width: 520px) {
body {
padding: 16px 12px;
}
.card {
padding: 22px;
}
h1 {
font-size: 1.35rem;
}
.row {
flex-direction: column;
gap: 0;
}
.estimate__from,
.estimate__to {
font-size: 1.7rem;
}
}(function () {
"use strict";
var form = document.getElementById("quoteForm");
var formCard = document.getElementById("formCard");
var resultCard = document.getElementById("resultCard");
var amountInput = document.getElementById("amount");
var COVERAGE_LABELS = {
auto: "auto insurance",
home: "home insurance",
renters: "renters insurance",
"term-life": "term life insurance",
health: "health insurance",
};
// Base monthly premium per $1,000 of coverage, by plan (illustrative only).
var RATE_PER_K = {
auto: 0.42,
home: 0.11,
renters: 0.05,
"term-life": 0.06,
health: 0.95,
};
// Live-format the coverage amount with thousands separators.
amountInput.addEventListener("input", function () {
var digits = amountInput.value.replace(/[^\d]/g, "").slice(0, 9);
amountInput.value = digits ? Number(digits).toLocaleString("en-US") : "";
});
function setError(id, message) {
var field = document.getElementById(id).closest(".field");
var err = document.getElementById(id + "-err");
field.classList.add("invalid");
err.textContent = message;
err.hidden = false;
}
function clearError(id) {
var field = document.getElementById(id).closest(".field");
var err = document.getElementById(id + "-err");
field.classList.remove("invalid");
err.hidden = true;
err.textContent = "";
}
function num(value) {
return Number(String(value).replace(/[^\d]/g, ""));
}
function validate() {
var ok = true;
var first = null;
var coverage = form.coverage.value;
if (!coverage) {
setError("coverage", "Please pick a coverage type.");
ok = false;
first = first || "coverage";
} else {
clearError("coverage");
}
var age = num(form.age.value);
if (!form.age.value) {
setError("age", "Enter your age.");
ok = false;
first = first || "age";
} else if (age < 18 || age > 99) {
setError("age", "Age must be between 18 and 99.");
ok = false;
first = first || "age";
} else {
clearError("age");
}
var zip = form.zip.value.trim();
if (!/^\d{5}$/.test(zip)) {
setError("zip", "Enter a valid 5-digit ZIP.");
ok = false;
first = first || "zip";
} else {
clearError("zip");
}
var amount = num(form.amount.value);
if (!amount) {
setError("amount", "Enter a coverage amount.");
ok = false;
first = first || "amount";
} else if (amount < 1000) {
setError("amount", "Coverage must be at least $1,000.");
ok = false;
first = first || "amount";
} else {
clearError("amount");
}
var name = form.name.value.trim();
if (name.length < 2) {
setError("name", "Please enter your full name.");
ok = false;
first = first || "name";
} else {
clearError("name");
}
var email = form.email.value.trim();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
setError("email", "Enter a valid email address.");
ok = false;
first = first || "email";
} else {
clearError("email");
}
if (first) {
document.getElementById(first).focus();
}
return ok;
}
function estimate(coverage, age, amount) {
var base = (amount / 1000) * (RATE_PER_K[coverage] || 0.1);
// Older applicants pay more; clamp so demo numbers stay sensible.
var ageFactor = 1 + Math.max(0, age - 25) * 0.012;
var monthly = base * ageFactor;
var low = Math.max(8, Math.round(monthly * 0.85));
var high = Math.round(monthly * 1.2) + 4;
return { low: low, high: high };
}
function money(n) {
return "$" + Number(n).toLocaleString("en-US");
}
form.addEventListener("submit", function (e) {
e.preventDefault();
if (!validate()) return;
var coverage = form.coverage.value;
var range = estimate(coverage, num(form.age.value), num(form.amount.value));
document.getElementById("resultName").textContent =
form.name.value.trim().split(" ")[0] || "there";
document.getElementById("resultCoverage").textContent =
COVERAGE_LABELS[coverage] || "coverage";
document.getElementById("resultEmail").textContent = form.email.value.trim();
document.getElementById("estLow").textContent = money(range.low);
document.getElementById("estHigh").textContent = money(range.high);
formCard.hidden = true;
resultCard.hidden = false;
resultCard.focus && resultCard.focus();
window.scrollTo({ top: 0, behavior: "smooth" });
});
// Clear a field's error as soon as the user starts fixing it.
["coverage", "age", "zip", "amount", "name", "email"].forEach(function (id) {
var el = document.getElementById(id);
var evt = el.tagName === "SELECT" ? "change" : "input";
el.addEventListener(evt, function () {
if (el.closest(".field").classList.contains("invalid")) clearError(id);
});
});
document.getElementById("restart").addEventListener("click", function () {
form.reset();
["coverage", "age", "zip", "amount", "name", "email"].forEach(clearError);
resultCard.hidden = true;
formCard.hidden = false;
form.coverage.focus();
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Get a Quote — Northwind Insurance</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="quote">
<!-- Quote form -->
<section class="card" id="formCard" aria-labelledby="quoteHeading">
<header class="card__head">
<span class="brand">
<svg viewBox="0 0 24 24" aria-hidden="true" class="brand__mark">
<path d="M12 2 4 5v6c0 5 3.4 8.5 8 11 4.6-2.5 8-6 8-11V5l-8-3Z" />
<path d="m8.5 12 2.3 2.3L15.8 9" class="brand__check" />
</svg>
<span class="brand__name">Northwind Insurance</span>
</span>
<h1 id="quoteHeading">Get your free quote</h1>
<p class="card__sub">Takes about 60 seconds. No spam, no obligation.</p>
<ul class="proof" aria-label="Why people choose us">
<li><strong>4.8/5</strong> from 12,400 reviews</li>
<li><strong>$0</strong> upfront</li>
<li>Cancel anytime</li>
</ul>
</header>
<form id="quoteForm" novalidate>
<div class="field">
<label for="coverage">Coverage type</label>
<select id="coverage" name="coverage" required aria-describedby="coverage-err">
<option value="" disabled selected>Choose a plan…</option>
<option value="auto">Auto insurance</option>
<option value="home">Home insurance</option>
<option value="renters">Renters insurance</option>
<option value="term-life">Term life insurance</option>
<option value="health">Health insurance</option>
</select>
<p class="error" id="coverage-err" hidden></p>
</div>
<div class="row">
<div class="field">
<label for="age">Your age</label>
<input
id="age"
name="age"
type="number"
inputmode="numeric"
min="18"
max="99"
placeholder="34"
required
aria-describedby="age-err"
/>
<p class="error" id="age-err" hidden></p>
</div>
<div class="field">
<label for="zip">ZIP code</label>
<input
id="zip"
name="zip"
type="text"
inputmode="numeric"
maxlength="5"
placeholder="60614"
required
aria-describedby="zip-err"
/>
<p class="error" id="zip-err" hidden></p>
</div>
</div>
<div class="field">
<label for="amount">Coverage amount</label>
<div class="amount">
<span class="amount__sym" aria-hidden="true">$</span>
<input
id="amount"
name="amount"
type="text"
inputmode="numeric"
placeholder="250,000"
required
aria-describedby="amount-err amount-hint"
/>
</div>
<p class="hint" id="amount-hint">How much protection you want, in USD.</p>
<p class="error" id="amount-err" hidden></p>
</div>
<hr class="rule" />
<div class="field">
<label for="name">Full name</label>
<input
id="name"
name="name"
type="text"
autocomplete="name"
placeholder="Maya Okonkwo"
required
aria-describedby="name-err"
/>
<p class="error" id="name-err" hidden></p>
</div>
<div class="field">
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
placeholder="[email protected]"
required
aria-describedby="email-err"
/>
<p class="error" id="email-err" hidden></p>
</div>
<button type="submit" class="submit">Get my estimate</button>
<p class="privacy">
<svg viewBox="0 0 24 24" aria-hidden="true" class="lock">
<rect x="5" y="11" width="14" height="9" rx="2" />
<path d="M8 11V8a4 4 0 0 1 8 0v3" />
</svg>
We use bank-level encryption. Your details are never sold — see our
<a href="#privacy">privacy policy</a>.
</p>
</form>
</section>
<!-- Success state -->
<section class="card card--done" id="resultCard" hidden aria-live="polite">
<div class="done__icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="m5 13 4 4L19 7" /></svg>
</div>
<h2>You're all set, <span id="resultName">there</span>!</h2>
<p class="done__sub">Here's your estimated <span id="resultCoverage">coverage</span> premium:</p>
<div class="estimate">
<span class="estimate__from" id="estLow">$0</span>
<span class="estimate__sep" aria-hidden="true">–</span>
<span class="estimate__to" id="estHigh">$0</span>
<span class="estimate__per">/ month</span>
</div>
<p class="done__note">
A licensed agent will email <strong id="resultEmail">you</strong> within one business day
with a personalized plan. No payment is needed to review it.
</p>
<button type="button" class="ghost" id="restart">Start a new quote</button>
</section>
</main>
<script src="script.js"></script>
</body>
</html>Get a Quote
A reassuring, trust-forward lead form for an insurance landing page. It opens with a confident headline and a few proof points, then collects a coverage type, the basics needed to size a premium, and contact details. Each field validates inline with clear error messages, and a privacy note sets expectations before anyone hits submit.
On a valid submit the form swaps to a calm success state: a big, confident estimated monthly premium range computed from the chosen coverage, age band and coverage amount, plus the next step and a button to start over. All logic is vanilla JS — no libraries, no network calls.
The numbers are illustrative ranges only, designed to feel realistic without committing to a real underwriting model.
Illustrative UI only — not a real insurance quote or binding offer.