<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<symbol id="lm-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
<symbol id="lm-i-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></symbol>
<symbol id="lm-i-list" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8l1.5 1.5L12 7M8 14l1.5 1.5L12 13M14.5 8.5H17M14.5 14.5H17"/></symbol>
<symbol id="lm-i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 6.5L12 13l8.5-6.5"/></symbol>
<symbol id="lm-i-doc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></symbol>
</svg>
<div><span class="lbl">Free guide</span>
<section class="lm lm-guide" aria-labelledby="sg-title">
<div class="lm-cover" aria-hidden="true"><small>Plot & Pot</small><b>The Small Garden Planting Calendar</b><span>2026 edition</span></div>
<div>
<span class="lm-kicker"><svg aria-hidden="true"><use href="#lm-i-doc"/></svg>Free PDF guide</span>
<h3 class="lm-title" id="sg-title">The Small Garden Planting Calendar</h3>
<p class="lm-meta">24 pages · month by month · for balconies, yards and raised beds</p>
<ul class="lm-list">
<li><svg aria-hidden="true"><use href="#lm-i-tick"/></svg>What to sow, plant out and harvest each month</li>
<li><svg aria-hidden="true"><use href="#lm-i-tick"/></svg>Twelve crops that earn their space in a small plot</li>
<li><svg aria-hidden="true"><use href="#lm-i-tick"/></svg>A printable wall planner</li>
</ul>
<form class="lm-form" action="#" method="post" data-nf="sg-done">
<div class="lm-fields">
<div><label class="lm-label" for="sg-name">First name <small>(optional)</small></label>
<input class="lm-input" id="sg-name" name="first-name" type="text" autocomplete="given-name"></div>
<div><label class="lm-label" for="sg-email">Email address</label>
<input class="lm-input" id="sg-email" name="email" type="email" autocomplete="email" required aria-describedby="sg-email-err sg-hint" data-err="sg-email-err"
data-missing="Enter your email address so we can send the guide." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<p class="lm-err" id="sg-email-err"></p></div>
</div>
<label class="lm-check"><input type="checkbox" name="newsletter" value="plot-notes"><span>Also send me Plot Notes, the monthly newsletter (optional)</span></label>
<button class="lm-btn" type="submit"><svg aria-hidden="true"><use href="#lm-i-down"/></svg>Send me the guide</button>
<p class="lm-hint" id="sg-hint">We email you a download link. That's it, unless you tick the box above. <a href="#privacy">Privacy</a></p>
</form>
<div class="lm-done" id="sg-done" tabindex="-1" hidden>
<div class="lm-done-row"><span class="lm-done-ico" aria-hidden="true"><svg><use href="#lm-i-mail"/></svg></span>
<div><b>Your calendar is on its way</b><p>Check <span data-email>your inbox</span> for the download link. It works for seven days.</p></div></div>
</div>
</div>
</section>
</div>
<div><span class="lbl">Content upgrade inside an article</span>
<article class="lm lm-article" aria-labelledby="fp-title">
<h3 id="fp-title">How to price your first freelance project</h3>
<p>Start from what the client is trying to achieve, not from your hours. A homepage redesign that lifts sign-ups is worth more to a subscription business than to a local bakery, even if the work takes you the same week.</p>
<aside class="lm-upgrade" aria-labelledby="fp-up-title">
<span class="lm-up-ico" aria-hidden="true"><svg><use href="#lm-i-list"/></svg></span>
<div>
<b id="fp-up-title">Get the one-page pricing checklist</b>
<p class="lm-text">The eight questions from this article, as a PDF to keep beside you on discovery calls.</p>
<form class="lm-form" action="#" method="post" data-nf="fp-done">
<label class="lm-label" for="fp-email">Email address</label>
<div class="lm-row">
<input class="lm-input" id="fp-email" name="email" type="email" autocomplete="email" required aria-describedby="fp-email-err fp-hint" data-err="fp-email-err"
data-missing="Enter your email address to get the checklist." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<button class="lm-btn" type="submit">Email me the checklist</button>
</div>
<p class="lm-err" id="fp-email-err"></p>
<p class="lm-hint" id="fp-hint">One email with the PDF. We won't add you to anything else.</p>
</form>
<div class="lm-done" id="fp-done" tabindex="-1" hidden>
<div class="lm-done-row"><span class="lm-done-ico" aria-hidden="true"><svg><use href="#lm-i-tick"/></svg></span>
<div><b>Sent</b><p>The checklist is on its way to <span data-email>you</span>. Keep reading below.</p></div></div>
</div>
</div>
</aside>
<p>Then write the price as a range with the trade-offs spelled out. Clients rarely haggle over a number they understand; they haggle over one that arrived without a reason.</p>
</article>
</div>
<div><span class="lbl">Five-day email course</span>
<section class="lm lm-course" aria-labelledby="sd-title">
<div class="lm-days">
<span class="lm-kicker"><svg aria-hidden="true"><use href="#lm-i-mail"/></svg>Free email course</span>
<h3 class="lm-title" id="sd-title">Sourdough without the stress</h3>
<ol>
<li><div><b>A starter that survives</b><span>Flour, water and a jar you already own</span></div></li>
<li><div><b>Reading the dough</b><span>When to fold and when to leave it alone</span></div></li>
<li><div><b>Shaping in two minutes</b><span>Short video, one technique</span></div></li>
<li><div><b>Baking without a Dutch oven</b><span>Steam from a roasting tin</span></div></li>
<li><div><b>Your weekly rhythm</b><span>A schedule that fits a working week</span></div></li>
</ol>
</div>
<div class="lm-join">
<h4>One lesson a day for five days</h4>
<p class="lm-text">Each email takes about four minutes to read. 11,300 people have taken it so far.</p>
<form class="lm-form" action="#" method="post" data-nf="sd-done">
<label class="lm-label" for="sd-email">Email address</label>
<input class="lm-input" id="sd-email" name="email" type="email" autocomplete="email" required aria-describedby="sd-email-err sd-hint" data-err="sd-email-err"
data-missing="Enter your email address to start the course." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<p class="lm-err" id="sd-email-err"></p>
<label class="lm-check"><input type="checkbox" name="newsletter" value="weekly-bake"><span>When the course ends, keep sending me The Weekly Bake (optional)</span></label>
<button class="lm-btn" type="submit">Start the course</button>
<p class="lm-hint" id="sd-hint">Lesson one arrives within ten minutes. <a href="#privacy">Privacy notice</a></p>
</form>
<div class="lm-done" id="sd-done" tabindex="-1" hidden>
<div class="lm-done-row"><span class="lm-done-ico" aria-hidden="true"><svg><use href="#lm-i-tick"/></svg></span>
<div><b>You're enrolled</b><p>Lesson one is heading to <span data-email>you</span>. Add us to your contacts so day two doesn't land in spam.</p></div></div>
</div>
</div>
</section>
</div>
CSS
/* Lead magnet sign-ups: a free guide, a content upgrade inside an article, and an email course.
Each says exactly what arrives and how often. The resource is the only thing the email field asks for;
joining the regular newsletter is a separate, optional box that starts unticked. */
.lm{--lm-ink:#1c2333;--lm-muted:#4f5668;--lm-line:#7a8091;--lm-accent:#1e3a8a;--lm-accent-dark:#172c6b;--lm-ring:rgba(30,58,138,.22);--lm-gold:#e7b73b;
--lm-error:#b42318;--lm-ok:#15803d;--lm-serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;color:var(--lm-ink);font-size:.92rem;line-height:1.5}
.lm a{color:var(--lm-accent);font-weight:600;text-underline-offset:2px}
.lm a:focus-visible{outline:2px solid var(--lm-accent);outline-offset:2px;border-radius:3px}
.lm-kicker{display:inline-flex;align-items:center;gap:6px;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lm-accent)}
.lm-kicker svg{width:15px;height:15px}
.lm-title{font-family:var(--lm-serif);font-size:1.6rem;font-weight:700;line-height:1.2;letter-spacing:-.01em;margin:6px 0 8px}
.lm-text{color:var(--lm-muted)}
.lm-label{display:block;font-size:.84rem;font-weight:650;margin-bottom:6px}
.lm-label small{font-weight:500;color:var(--lm-muted);font-size:.8rem}
.lm-input{display:block;width:100%;min-width:0;font:inherit;color:var(--lm-ink);background:#fff;border:1.5px solid var(--lm-line);border-radius:10px;padding:10px 13px;transition:border-color .15s,box-shadow .15s}
.lm-input:focus{outline:none;border-color:var(--lm-accent);box-shadow:0 0 0 4px var(--lm-ring)}
.lm-input.is-invalid{border-color:var(--lm-error)}
.lm-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font:inherit;font-weight:650;color:#fff;background:var(--lm-accent);border:0;border-radius:10px;padding:11px 20px;cursor:pointer;transition:background-color .15s}
.lm-btn:hover{background:var(--lm-accent-dark)}
.lm-btn:focus-visible{outline:2px solid var(--lm-accent);outline-offset:3px}
.lm-btn svg{width:18px;height:18px}
.lm-err{display:flex;gap:7px;align-items:flex-start;font-size:.82rem;font-weight:600;color:var(--lm-error);margin-top:7px}
.lm-err:empty{display:none}
.lm-err::before{content:"!";flex:none;display:grid;place-items:center;width:16px;height:16px;margin-top:2px;border-radius:50%;background:var(--lm-error);color:#fff;font-size:.7rem;font-weight:800}
.lm-hint{font-size:.78rem;color:var(--lm-muted);margin-top:8px}
.lm-check{display:flex;gap:10px;align-items:flex-start;font-size:.84rem;cursor:pointer;margin-top:14px}
.lm-check input{appearance:none;flex:none;width:18px;height:18px;margin:2px 0 0;border:1.5px solid var(--lm-line);border-radius:5px;background:#fff center/12px no-repeat;cursor:pointer;transition:background-color .15s,border-color .15s}
.lm-check input:checked{background-color:var(--lm-accent);border-color:var(--lm-accent);
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23fff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}
.lm-check input:focus-visible{outline:2px solid var(--lm-accent);outline-offset:2px}
.lm-done{outline:none}
.lm-done[hidden],.lm-form[hidden]{display:none}
.lm-done:focus-visible{outline:2px solid var(--lm-accent);outline-offset:4px;border-radius:12px}
.lm-done-row{display:flex;gap:14px;align-items:flex-start;background:#eef2fb;border-radius:14px;padding:16px 18px}
.lm-done-ico{flex:none;display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:var(--lm-accent);color:#fff}
.lm-done-ico svg{width:20px;height:20px}
.lm-done b{display:block;font-size:1rem}
.lm-done p{color:var(--lm-muted)}
.lm-done span[data-email]{color:var(--lm-ink);font-weight:600;overflow-wrap:anywhere}
/* 1. Free guide with a cover drawn in CSS */
.lm-guide{display:grid;grid-template-columns:230px 1fr;gap:36px;align-items:center;background:#fff;border-radius:22px;padding:32px 34px;
box-shadow:0 1px 2px rgba(28,35,51,.06),0 22px 48px -28px rgba(28,35,51,.4)}
.lm-cover{position:relative;aspect-ratio:3/4;border-radius:6px 14px 14px 6px;padding:22px 18px 18px 26px;color:#1c2333;display:flex;flex-direction:column;
background:radial-gradient(circle at 78% 70%,#c2410c 0 16px,transparent 17px),radial-gradient(circle at 60% 82%,#15803d 0 22px,transparent 23px),
radial-gradient(circle at 86% 88%,#166534 0 26px,transparent 27px),linear-gradient(180deg,var(--lm-gold),#f3cf6e);
box-shadow:inset 8px 0 0 rgba(28,35,51,.18),0 20px 30px -14px rgba(28,35,51,.5);transform:rotate(-3deg)}
.lm-cover small{font-size:.62rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}
.lm-cover b{font-family:var(--lm-serif);font-size:1.35rem;line-height:1.1;margin-top:8px}
.lm-cover span{font-size:.7rem;font-weight:600;margin-top:8px}
.lm-meta{font-size:.8rem;color:var(--lm-muted);font-weight:600}
.lm-list{list-style:none;display:grid;gap:6px;margin:14px 0 18px}
.lm-list li{display:flex;gap:9px;align-items:flex-start}
.lm-list svg{flex:none;width:18px;height:18px;margin-top:1px;color:var(--lm-ok)}
.lm-fields{display:grid;grid-template-columns:1fr 1.4fr;gap:10px}
.lm-guide .lm-btn{margin-top:14px}
/* 2. Content upgrade inside an article */
.lm-article{background:#fff;border-radius:22px;padding:34px 40px;box-shadow:0 1px 2px rgba(28,35,51,.06),0 22px 48px -28px rgba(28,35,51,.4)}
.lm-article h3{font-family:var(--lm-serif);font-size:1.45rem;line-height:1.25;margin-bottom:10px}
.lm-article>p{font-family:var(--lm-serif);font-size:1.05rem;line-height:1.65;color:#2f3647}
.lm-upgrade{display:grid;grid-template-columns:auto 1fr;gap:16px;margin:22px 0;padding:20px 22px;border:2px dashed #9fb0db;border-radius:16px;background:#f5f7fd}
.lm-up-ico{display:grid;place-items:center;width:46px;height:46px;border-radius:12px;background:var(--lm-accent);color:#fff}
.lm-up-ico svg{width:24px;height:24px}
.lm-upgrade b{display:block;font-size:1.02rem}
.lm-upgrade .lm-form{margin-top:12px}
.lm-row{display:flex;gap:8px}
.lm-row .lm-input{flex:1 1 0}
.lm-upgrade .lm-done{margin-top:12px}
/* 3. Five-day email course */
.lm-course{display:grid;grid-template-columns:1fr 1fr;border-radius:22px;overflow:hidden;background:#fff;box-shadow:0 1px 2px rgba(28,35,51,.06),0 22px 48px -28px rgba(28,35,51,.4)}
.lm-days{background:#1c2333;color:#e8ebf2;padding:30px 30px 26px}
.lm-days .lm-kicker{color:var(--lm-gold)}
.lm-days .lm-title{color:#fff}
.lm-days ol{list-style:none;display:grid;gap:10px;margin-top:16px;counter-reset:day}
.lm-days li{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:start;counter-increment:day}
.lm-days li::before{content:counter(day);display:grid;place-items:center;width:28px;height:28px;border-radius:8px;background:rgba(231,183,59,.16);color:var(--lm-gold);font-weight:800;font-size:.85rem}
.lm-days li b{display:block;color:#fff;font-size:.92rem}
.lm-days li span{font-size:.82rem;color:#b9c0cf}
.lm-join{padding:30px 30px 26px;display:flex;flex-direction:column;justify-content:center}
.lm-join h4{font-size:1.1rem;font-weight:750}
.lm-join .lm-text{margin:4px 0 16px}
.lm-join .lm-btn{width:100%;margin-top:14px}
@media (max-width:720px){.lm-guide{grid-template-columns:1fr;gap:26px;padding:26px 22px}.lm-cover{width:170px;margin:0 auto}
.lm-course{grid-template-columns:1fr}.lm-article{padding:26px 22px}}
@media (max-width:420px){.lm-fields{grid-template-columns:1fr}.lm-row{flex-direction:column}.lm-upgrade{grid-template-columns:1fr;padding:18px 16px}
.lm-days,.lm-join{padding:24px 20px 22px}.lm-guide .lm-btn{width:100%}}
@media (prefers-reduced-motion:reduce){.lm-input,.lm-btn,.lm-check input{transition:none}}
JavaScript
// Front-end demo only: nothing is sent. On submit the script checks each field that has data-err, writes the message
// into the element the input already points to with aria-describedby, and moves focus to the first problem.
// A valid submit swaps the form for its success state and focuses it. Replace that branch with your fetch() call.
// Without JavaScript the browser validates the form itself and posts it to the action URL.
(function () {
function check(el) {
var msg = el.checkValidity() ? '' : el.getAttribute(el.validity.valueMissing ? 'data-missing' : 'data-bad');
var err = document.getElementById(el.getAttribute('data-err'));
if (err) err.textContent = msg || '';
el.classList.toggle('is-invalid', !!msg);
if (msg) el.setAttribute('aria-invalid', 'true'); else el.removeAttribute('aria-invalid');
return !msg;
}
document.querySelectorAll('form[data-nf]').forEach(function (form) {
var done = document.getElementById(form.getAttribute('data-nf'));
var email = form.querySelector('input[type=email]');
form.noValidate = true;
form.addEventListener('submit', function (e) {
e.preventDefault();
var first = null;
form.querySelectorAll('[data-err]').forEach(function (el) { if (!check(el) && !first) first = el; });
if (first) { first.focus(); return; }
done.querySelectorAll('[data-email]').forEach(function (n) { n.textContent = email.value; });
form.hidden = true; done.hidden = false; done.focus();
});
form.addEventListener('input', function (e) { if (e.target.classList.contains('is-invalid')) check(e.target); });
});
})();
Bootstrap 5 code
Requires: Bootstrap 5.3.8 CSS
HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<symbol id="lm-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
<symbol id="lm-i-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></symbol>
<symbol id="lm-i-list" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8l1.5 1.5L12 7M8 14l1.5 1.5L12 13M14.5 8.5H17M14.5 14.5H17"/></symbol>
<symbol id="lm-i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 6.5L12 13l8.5-6.5"/></symbol>
<symbol id="lm-i-doc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></symbol>
</svg>
<div><span class="lbl">Free guide</span>
<section class="card lm lm-guide" aria-labelledby="sg-title"><div class="card-body">
<div class="lm-cover" aria-hidden="true"><small>Plot & Pot</small><b>The Small Garden Planting Calendar</b><span>2026 edition</span></div>
<div>
<span class="lm-kicker"><svg aria-hidden="true"><use href="#lm-i-doc"/></svg>Free PDF guide</span>
<h3 class="lm-title" id="sg-title">The Small Garden Planting Calendar</h3>
<p class="lm-meta">24 pages · month by month · for balconies, yards and raised beds</p>
<ul class="list-unstyled lm-list">
<li><svg aria-hidden="true"><use href="#lm-i-tick"/></svg>What to sow, plant out and harvest each month</li>
<li><svg aria-hidden="true"><use href="#lm-i-tick"/></svg>Twelve crops that earn their space in a small plot</li>
<li><svg aria-hidden="true"><use href="#lm-i-tick"/></svg>A printable wall planner</li>
</ul>
<form action="#" method="post" data-nf="sg-done">
<div class="lm-fields">
<div><label class="form-label" for="sg-name">First name <small>(optional)</small></label>
<input class="form-control" id="sg-name" name="first-name" type="text" autocomplete="given-name"></div>
<div><label class="form-label" for="sg-email">Email address</label>
<input class="form-control" id="sg-email" name="email" type="email" autocomplete="email" required aria-describedby="sg-email-err sg-hint" data-err="sg-email-err"
data-missing="Enter your email address so we can send the guide." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<p class="lm-err" id="sg-email-err"></p></div>
</div>
<div class="form-check"><input class="form-check-input" type="checkbox" name="newsletter" value="plot-notes" id="sg-news"><label class="form-check-label" for="sg-news">Also send me Plot Notes, the monthly newsletter (optional)</label></div>
<button class="btn lm-btn" type="submit"><svg aria-hidden="true"><use href="#lm-i-down"/></svg>Send me the guide</button>
<p class="form-text" id="sg-hint">We email you a download link. That's it, unless you tick the box above. <a href="#privacy">Privacy</a></p>
</form>
<div class="lm-done" id="sg-done" tabindex="-1" hidden>
<div class="lm-done-row"><span class="lm-done-ico" aria-hidden="true"><svg><use href="#lm-i-mail"/></svg></span>
<div><b>Your calendar is on its way</b><p>Check <span data-email>your inbox</span> for the download link. It works for seven days.</p></div></div>
</div>
</div>
</div></section>
</div>
<div><span class="lbl">Content upgrade inside an article</span>
<article class="card lm lm-article" aria-labelledby="fp-title"><div class="card-body">
<h3 id="fp-title">How to price your first freelance project</h3>
<p>Start from what the client is trying to achieve, not from your hours. A homepage redesign that lifts sign-ups is worth more to a subscription business than to a local bakery, even if the work takes you the same week.</p>
<aside class="lm-upgrade" aria-labelledby="fp-up-title">
<span class="lm-up-ico" aria-hidden="true"><svg><use href="#lm-i-list"/></svg></span>
<div>
<b id="fp-up-title">Get the one-page pricing checklist</b>
<p class="lm-text">The eight questions from this article, as a PDF to keep beside you on discovery calls.</p>
<form action="#" method="post" data-nf="fp-done">
<label class="form-label" for="fp-email">Email address</label>
<div class="d-flex gap-2 lm-row">
<input class="form-control" id="fp-email" name="email" type="email" autocomplete="email" required aria-describedby="fp-email-err fp-hint" data-err="fp-email-err"
data-missing="Enter your email address to get the checklist." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<button class="btn lm-btn" type="submit">Email me the checklist</button>
</div>
<p class="lm-err" id="fp-email-err"></p>
<p class="form-text" id="fp-hint">One email with the PDF. We won't add you to anything else.</p>
</form>
<div class="lm-done" id="fp-done" tabindex="-1" hidden>
<div class="lm-done-row"><span class="lm-done-ico" aria-hidden="true"><svg><use href="#lm-i-tick"/></svg></span>
<div><b>Sent</b><p>The checklist is on its way to <span data-email>you</span>. Keep reading below.</p></div></div>
</div>
</div>
</aside>
<p>Then write the price as a range with the trade-offs spelled out. Clients rarely haggle over a number they understand; they haggle over one that arrived without a reason.</p>
</div></article>
</div>
<div><span class="lbl">Five-day email course</span>
<section class="card lm lm-course" aria-labelledby="sd-title">
<div class="row">
<div class="col-12 col-md-6 lm-days">
<span class="lm-kicker"><svg aria-hidden="true"><use href="#lm-i-mail"/></svg>Free email course</span>
<h3 class="lm-title" id="sd-title">Sourdough without the stress</h3>
<ol class="list-unstyled">
<li><div><b>A starter that survives</b><span>Flour, water and a jar you already own</span></div></li>
<li><div><b>Reading the dough</b><span>When to fold and when to leave it alone</span></div></li>
<li><div><b>Shaping in two minutes</b><span>Short video, one technique</span></div></li>
<li><div><b>Baking without a Dutch oven</b><span>Steam from a roasting tin</span></div></li>
<li><div><b>Your weekly rhythm</b><span>A schedule that fits a working week</span></div></li>
</ol>
</div>
<div class="col-12 col-md-6 lm-join">
<h4>One lesson a day for five days</h4>
<p class="lm-text">Each email takes about four minutes to read. 11,300 people have taken it so far.</p>
<form action="#" method="post" data-nf="sd-done">
<label class="form-label" for="sd-email">Email address</label>
<input class="form-control" id="sd-email" name="email" type="email" autocomplete="email" required aria-describedby="sd-email-err sd-hint" data-err="sd-email-err"
data-missing="Enter your email address to start the course." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<p class="lm-err" id="sd-email-err"></p>
<div class="form-check"><input class="form-check-input" type="checkbox" name="newsletter" value="weekly-bake" id="sd-news"><label class="form-check-label" for="sd-news">When the course ends, keep sending me The Weekly Bake (optional)</label></div>
<button class="btn lm-btn" type="submit">Start the course</button>
<p class="form-text" id="sd-hint">Lesson one arrives within ten minutes. <a href="#privacy">Privacy notice</a></p>
</form>
<div class="lm-done" id="sd-done" tabindex="-1" hidden>
<div class="lm-done-row"><span class="lm-done-ico" aria-hidden="true"><svg><use href="#lm-i-tick"/></svg></span>
<div><b>You're enrolled</b><p>Lesson one is heading to <span data-email>you</span>. Add us to your contacts so day two doesn't land in spam.</p></div></div>
</div>
</div>
</div>
</section>
</div>
// Front-end demo only: nothing is sent. On submit the script checks each field that has data-err, writes the message
// into the element the input already points to with aria-describedby, and moves focus to the first problem.
// A valid submit swaps the form for its success state and focuses it. Replace that branch with your fetch() call.
// Without JavaScript the browser validates the form itself and posts it to the action URL.
(function () {
function check(el) {
var msg = el.checkValidity() ? '' : el.getAttribute(el.validity.valueMissing ? 'data-missing' : 'data-bad');
var err = document.getElementById(el.getAttribute('data-err'));
if (err) err.textContent = msg || '';
el.classList.toggle('is-invalid', !!msg);
if (msg) el.setAttribute('aria-invalid', 'true'); else el.removeAttribute('aria-invalid');
return !msg;
}
document.querySelectorAll('form[data-nf]').forEach(function (form) {
var done = document.getElementById(form.getAttribute('data-nf'));
var email = form.querySelector('input[type=email]');
form.noValidate = true;
form.addEventListener('submit', function (e) {
e.preventDefault();
var first = null;
form.querySelectorAll('[data-err]').forEach(function (el) { if (!check(el) && !first) first = el; });
if (first) { first.focus(); return; }
done.querySelectorAll('[data-email]').forEach(function (n) { n.textContent = email.value; });
form.hidden = true; done.hidden = false; done.focus();
});
form.addEventListener('input', function (e) { if (e.target.classList.contains('is-invalid')) check(e.target); });
});
})();
Tailwind 4 code
Requires: Tailwind CSS 4
HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<symbol id="lm-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
<symbol id="lm-i-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></symbol>
<symbol id="lm-i-list" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8l1.5 1.5L12 7M8 14l1.5 1.5L12 13M14.5 8.5H17M14.5 14.5H17"/></symbol>
<symbol id="lm-i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 6.5L12 13l8.5-6.5"/></symbol>
<symbol id="lm-i-doc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></symbol>
</svg>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5d5a52] uppercase">Free guide</span>
<section class="grid grid-cols-[230px_1fr] items-center gap-9 rounded-[22px] bg-white px-[34px] py-8 text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(28,35,51,.06),0_22px_48px_-28px_rgba(28,35,51,.4)] max-[720px]:grid-cols-1 max-[720px]:gap-[26px] max-[720px]:px-[22px] max-[720px]:py-[26px]" aria-labelledby="sg-title">
<div class="relative flex aspect-[3/4] -rotate-3 flex-col rounded-[6px_14px_14px_6px] bg-lm-cover pt-[22px] pr-[18px] pb-[18px] pl-[26px] text-lm-ink shadow-[inset_8px_0_0_rgba(28,35,51,.18),0_20px_30px_-14px_rgba(28,35,51,.5)] max-[720px]:mx-auto max-[720px]:w-[170px]" aria-hidden="true"><small class="text-[.62rem] font-extrabold tracking-[.14em] uppercase">Plot & Pot</small><b class="mt-2 font-lm-serif text-[1.35rem] leading-[1.1]">The Small Garden Planting Calendar</b><span class="mt-2 text-[.7rem] font-semibold">2026 edition</span></div>
<div>
<span class="inline-flex items-center gap-1.5 text-[.72rem] font-bold tracking-[.1em] text-lm-accent uppercase"><svg class="size-[15px]" aria-hidden="true"><use href="#lm-i-doc"/></svg>Free PDF guide</span>
<h3 class="mt-1.5 mb-2 font-lm-serif text-[1.6rem] leading-[1.2] font-bold tracking-[-.01em]" id="sg-title">The Small Garden Planting Calendar</h3>
<p class="text-[.8rem] font-semibold text-lm-muted">24 pages · month by month · for balconies, yards and raised beds</p>
<ul class="mt-3.5 mb-[18px] grid gap-1.5">
<li class="flex items-start gap-[9px]"><svg class="mt-px size-[18px] flex-none text-lm-ok" aria-hidden="true"><use href="#lm-i-tick"/></svg>What to sow, plant out and harvest each month</li>
<li class="flex items-start gap-[9px]"><svg class="mt-px size-[18px] flex-none text-lm-ok" aria-hidden="true"><use href="#lm-i-tick"/></svg>Twelve crops that earn their space in a small plot</li>
<li class="flex items-start gap-[9px]"><svg class="mt-px size-[18px] flex-none text-lm-ok" aria-hidden="true"><use href="#lm-i-tick"/></svg>A printable wall planner</li>
</ul>
<form action="#" method="post" data-nf="sg-done">
<div class="grid grid-cols-[1fr_1.4fr] gap-2.5 max-[420px]:grid-cols-1">
<div><label class="mb-1.5 block text-[.84rem] font-[650]" for="sg-name">First name <small class="text-[.8rem] font-medium text-lm-muted">(optional)</small></label>
<input class="block w-full min-w-0 rounded-[10px] border-[1.5px] border-lm-line bg-white px-[13px] py-2.5 text-lm-ink transition-[border-color,box-shadow] duration-150 focus:border-lm-accent focus:shadow-[0_0_0_4px_rgba(30,58,138,.22)] focus:outline-none motion-reduce:transition-none" id="sg-name" name="first-name" type="text" autocomplete="given-name"></div>
<div><label class="mb-1.5 block text-[.84rem] font-[650]" for="sg-email">Email address</label>
<input class="block w-full min-w-0 rounded-[10px] border-[1.5px] border-lm-line bg-white px-[13px] py-2.5 text-lm-ink transition-[border-color,box-shadow] duration-150 focus:border-lm-accent focus:shadow-[0_0_0_4px_rgba(30,58,138,.22)] focus:outline-none [&.is-invalid]:border-lm-error motion-reduce:transition-none" id="sg-email" name="email" type="email" autocomplete="email" required aria-describedby="sg-email-err sg-hint" data-err="sg-email-err"
data-missing="Enter your email address so we can send the guide." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<p class="mt-[7px] flex items-start gap-[7px] text-[.82rem] font-semibold text-lm-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-lm-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="sg-email-err"></p></div>
</div>
<label class="mt-3.5 flex cursor-pointer items-start gap-2.5 text-[.84rem]"><input class="mt-0.5 size-[18px] flex-none cursor-pointer appearance-none rounded-[5px] border-[1.5px] border-lm-line bg-white bg-size-[12px] bg-center bg-no-repeat transition-colors duration-150 checked:border-lm-accent checked:bg-lm-accent checked:bg-lm-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-lm-accent motion-reduce:transition-none" type="checkbox" name="newsletter" value="plot-notes"><span>Also send me Plot Notes, the monthly newsletter (optional)</span></label>
<button class="mt-3.5 inline-flex cursor-pointer items-center justify-center gap-2 rounded-[10px] bg-lm-accent px-5 py-[11px] font-[650] text-white transition-colors duration-150 hover:bg-lm-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-lm-accent motion-reduce:transition-none max-[420px]:w-full" type="submit"><svg class="size-[18px]" aria-hidden="true"><use href="#lm-i-down"/></svg>Send me the guide</button>
<p class="mt-2 text-[.78rem] text-lm-muted" id="sg-hint">We email you a download link. That's it, unless you tick the box above. <a class="font-semibold text-lm-accent underline underline-offset-2 focus-visible:rounded-[3px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-lm-accent" href="#privacy">Privacy</a></p>
</form>
<div class="focus-visible:rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-lm-accent" id="sg-done" tabindex="-1" hidden>
<div class="flex items-start gap-3.5 rounded-[14px] bg-[#eef2fb] px-[18px] py-4"><span class="grid size-10 flex-none place-items-center rounded-full bg-lm-accent text-white" aria-hidden="true"><svg class="size-5"><use href="#lm-i-mail"/></svg></span>
<div><b class="block text-base">Your calendar is on its way</b><p class="text-lm-muted">Check <span class="font-semibold wrap-anywhere text-lm-ink" data-email>your inbox</span> for the download link. It works for seven days.</p></div></div>
</div>
</div>
</section>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5d5a52] uppercase">Content upgrade inside an article</span>
<article class="rounded-[22px] bg-white px-10 py-[34px] text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(28,35,51,.06),0_22px_48px_-28px_rgba(28,35,51,.4)] max-[720px]:px-[22px] max-[720px]:py-[26px]" aria-labelledby="fp-title">
<h3 class="mb-2.5 font-lm-serif text-[1.45rem] leading-[1.25] font-bold" id="fp-title">How to price your first freelance project</h3>
<p class="font-lm-serif text-[1.05rem] leading-[1.65] text-[#2f3647]">Start from what the client is trying to achieve, not from your hours. A homepage redesign that lifts sign-ups is worth more to a subscription business than to a local bakery, even if the work takes you the same week.</p>
<aside class="my-[22px] grid grid-cols-[auto_1fr] gap-4 rounded-2xl border-2 border-dashed border-[#9fb0db] bg-[#f5f7fd] px-[22px] py-5 max-[420px]:grid-cols-1 max-[420px]:px-4 max-[420px]:py-[18px]" aria-labelledby="fp-up-title">
<span class="grid size-[46px] place-items-center rounded-xl bg-lm-accent text-white" aria-hidden="true"><svg class="size-6"><use href="#lm-i-list"/></svg></span>
<div>
<b class="block text-[1.02rem]" id="fp-up-title">Get the one-page pricing checklist</b>
<p class="text-lm-muted">The eight questions from this article, as a PDF to keep beside you on discovery calls.</p>
<form class="mt-3" action="#" method="post" data-nf="fp-done">
<label class="mb-1.5 block text-[.84rem] font-[650]" for="fp-email">Email address</label>
<div class="flex gap-2 max-[420px]:flex-col">
<input class="block w-full min-w-0 flex-1 basis-0 rounded-[10px] border-[1.5px] border-lm-line bg-white px-[13px] py-2.5 text-lm-ink transition-[border-color,box-shadow] duration-150 focus:border-lm-accent focus:shadow-[0_0_0_4px_rgba(30,58,138,.22)] focus:outline-none [&.is-invalid]:border-lm-error motion-reduce:transition-none max-[420px]:basis-auto" id="fp-email" name="email" type="email" autocomplete="email" required aria-describedby="fp-email-err fp-hint" data-err="fp-email-err"
data-missing="Enter your email address to get the checklist." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<button class="inline-flex cursor-pointer items-center justify-center gap-2 rounded-[10px] bg-lm-accent px-5 py-[11px] font-[650] text-white transition-colors duration-150 hover:bg-lm-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-lm-accent motion-reduce:transition-none" type="submit">Email me the checklist</button>
</div>
<p class="mt-[7px] flex items-start gap-[7px] text-[.82rem] font-semibold text-lm-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-lm-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="fp-email-err"></p>
<p class="mt-2 text-[.78rem] text-lm-muted" id="fp-hint">One email with the PDF. We won't add you to anything else.</p>
</form>
<div class="mt-3 focus-visible:rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-lm-accent" id="fp-done" tabindex="-1" hidden>
<div class="flex items-start gap-3.5 rounded-[14px] bg-[#eef2fb] px-[18px] py-4"><span class="grid size-10 flex-none place-items-center rounded-full bg-lm-accent text-white" aria-hidden="true"><svg class="size-5"><use href="#lm-i-tick"/></svg></span>
<div><b class="block text-base">Sent</b><p class="text-lm-muted">The checklist is on its way to <span class="font-semibold wrap-anywhere text-lm-ink" data-email>you</span>. Keep reading below.</p></div></div>
</div>
</div>
</aside>
<p class="font-lm-serif text-[1.05rem] leading-[1.65] text-[#2f3647]">Then write the price as a range with the trade-offs spelled out. Clients rarely haggle over a number they understand; they haggle over one that arrived without a reason.</p>
</article>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5d5a52] uppercase">Five-day email course</span>
<section class="grid grid-cols-2 overflow-hidden rounded-[22px] bg-white text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(28,35,51,.06),0_22px_48px_-28px_rgba(28,35,51,.4)] max-[720px]:grid-cols-1" aria-labelledby="sd-title">
<div class="bg-lm-ink px-[30px] pt-[30px] pb-[26px] text-[#e8ebf2] max-[420px]:px-5 max-[420px]:pt-6 max-[420px]:pb-[22px]">
<span class="inline-flex items-center gap-1.5 text-[.72rem] font-bold tracking-[.1em] text-lm-gold uppercase"><svg class="size-[15px]" aria-hidden="true"><use href="#lm-i-mail"/></svg>Free email course</span>
<h3 class="mt-1.5 mb-2 font-lm-serif text-[1.6rem] leading-[1.2] font-bold tracking-[-.01em] text-white" id="sd-title">Sourdough without the stress</h3>
<ol class="mt-4 grid gap-2.5 [counter-reset:day]">
<li class="grid grid-cols-[auto_1fr] items-start gap-3 [counter-increment:day] before:grid before:size-7 before:place-items-center before:rounded-lg before:bg-[rgba(231,183,59,.16)] before:text-[.85rem] before:font-extrabold before:text-lm-gold before:content-[counter(day)]"><div><b class="block text-[.92rem] text-white">A starter that survives</b><span class="text-[.82rem] text-[#b9c0cf]">Flour, water and a jar you already own</span></div></li>
<li class="grid grid-cols-[auto_1fr] items-start gap-3 [counter-increment:day] before:grid before:size-7 before:place-items-center before:rounded-lg before:bg-[rgba(231,183,59,.16)] before:text-[.85rem] before:font-extrabold before:text-lm-gold before:content-[counter(day)]"><div><b class="block text-[.92rem] text-white">Reading the dough</b><span class="text-[.82rem] text-[#b9c0cf]">When to fold and when to leave it alone</span></div></li>
<li class="grid grid-cols-[auto_1fr] items-start gap-3 [counter-increment:day] before:grid before:size-7 before:place-items-center before:rounded-lg before:bg-[rgba(231,183,59,.16)] before:text-[.85rem] before:font-extrabold before:text-lm-gold before:content-[counter(day)]"><div><b class="block text-[.92rem] text-white">Shaping in two minutes</b><span class="text-[.82rem] text-[#b9c0cf]">Short video, one technique</span></div></li>
<li class="grid grid-cols-[auto_1fr] items-start gap-3 [counter-increment:day] before:grid before:size-7 before:place-items-center before:rounded-lg before:bg-[rgba(231,183,59,.16)] before:text-[.85rem] before:font-extrabold before:text-lm-gold before:content-[counter(day)]"><div><b class="block text-[.92rem] text-white">Baking without a Dutch oven</b><span class="text-[.82rem] text-[#b9c0cf]">Steam from a roasting tin</span></div></li>
<li class="grid grid-cols-[auto_1fr] items-start gap-3 [counter-increment:day] before:grid before:size-7 before:place-items-center before:rounded-lg before:bg-[rgba(231,183,59,.16)] before:text-[.85rem] before:font-extrabold before:text-lm-gold before:content-[counter(day)]"><div><b class="block text-[.92rem] text-white">Your weekly rhythm</b><span class="text-[.82rem] text-[#b9c0cf]">A schedule that fits a working week</span></div></li>
</ol>
</div>
<div class="flex flex-col justify-center px-[30px] pt-[30px] pb-[26px] max-[420px]:px-5 max-[420px]:pt-6 max-[420px]:pb-[22px]">
<h4 class="text-[1.1rem] font-[750]">One lesson a day for five days</h4>
<p class="mt-1 mb-4 text-lm-muted">Each email takes about four minutes to read. 11,300 people have taken it so far.</p>
<form action="#" method="post" data-nf="sd-done">
<label class="mb-1.5 block text-[.84rem] font-[650]" for="sd-email">Email address</label>
<input class="block w-full min-w-0 rounded-[10px] border-[1.5px] border-lm-line bg-white px-[13px] py-2.5 text-lm-ink transition-[border-color,box-shadow] duration-150 focus:border-lm-accent focus:shadow-[0_0_0_4px_rgba(30,58,138,.22)] focus:outline-none [&.is-invalid]:border-lm-error motion-reduce:transition-none" id="sd-email" name="email" type="email" autocomplete="email" required aria-describedby="sd-email-err sd-hint" data-err="sd-email-err"
data-missing="Enter your email address to start the course." data-bad="Enter an email address with an @ and a domain, like [email protected].">
<p class="mt-[7px] flex items-start gap-[7px] text-[.82rem] font-semibold text-lm-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-lm-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="sd-email-err"></p>
<label class="mt-3.5 flex cursor-pointer items-start gap-2.5 text-[.84rem]"><input class="mt-0.5 size-[18px] flex-none cursor-pointer appearance-none rounded-[5px] border-[1.5px] border-lm-line bg-white bg-size-[12px] bg-center bg-no-repeat transition-colors duration-150 checked:border-lm-accent checked:bg-lm-accent checked:bg-lm-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-lm-accent motion-reduce:transition-none" type="checkbox" name="newsletter" value="weekly-bake"><span>When the course ends, keep sending me The Weekly Bake (optional)</span></label>
<button class="mt-3.5 inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-[10px] bg-lm-accent px-5 py-[11px] font-[650] text-white transition-colors duration-150 hover:bg-lm-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-lm-accent motion-reduce:transition-none" type="submit">Start the course</button>
<p class="mt-2 text-[.78rem] text-lm-muted" id="sd-hint">Lesson one arrives within ten minutes. <a class="font-semibold text-lm-accent underline underline-offset-2 focus-visible:rounded-[3px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-lm-accent" href="#privacy">Privacy notice</a></p>
</form>
<div class="focus-visible:rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-lm-accent" id="sd-done" tabindex="-1" hidden>
<div class="flex items-start gap-3.5 rounded-[14px] bg-[#eef2fb] px-[18px] py-4"><span class="grid size-10 flex-none place-items-center rounded-full bg-lm-accent text-white" aria-hidden="true"><svg class="size-5"><use href="#lm-i-tick"/></svg></span>
<div><b class="block text-base">You're enrolled</b><p class="text-lm-muted">Lesson one is heading to <span class="font-semibold wrap-anywhere text-lm-ink" data-email>you</span>. Add us to your contacts so day two doesn't land in spam.</p></div></div>
</div>
</div>
</section>
</div>
// Front-end demo only: nothing is sent. On submit the script checks each field that has data-err, writes the message
// into the element the input already points to with aria-describedby, and moves focus to the first problem.
// A valid submit swaps the form for its success state and focuses it. Replace that branch with your fetch() call.
// Without JavaScript the browser validates the form itself and posts it to the action URL.
(function () {
function check(el) {
var msg = el.checkValidity() ? '' : el.getAttribute(el.validity.valueMissing ? 'data-missing' : 'data-bad');
var err = document.getElementById(el.getAttribute('data-err'));
if (err) err.textContent = msg || '';
el.classList.toggle('is-invalid', !!msg);
if (msg) el.setAttribute('aria-invalid', 'true'); else el.removeAttribute('aria-invalid');
return !msg;
}
document.querySelectorAll('form[data-nf]').forEach(function (form) {
var done = document.getElementById(form.getAttribute('data-nf'));
var email = form.querySelector('input[type=email]');
form.noValidate = true;
form.addEventListener('submit', function (e) {
e.preventDefault();
var first = null;
form.querySelectorAll('[data-err]').forEach(function (el) { if (!check(el) && !first) first = el; });
if (first) { first.focus(); return; }
done.querySelectorAll('[data-email]').forEach(function (n) { n.textContent = email.value; });
form.hidden = true; done.hidden = false; done.focus();
});
form.addEventListener('input', function (e) { if (e.target.classList.contains('is-invalid')) check(e.target); });
});
})();
About this set
Three sign-ups that offer something specific in return for an email address. Free guide shows a gardening planting calendar as a tilted book cover drawn in CSS, with its page count, three bullet points of what is inside, an optional first name field, the email field and a Send me the guide button. Content upgrade inside an article sits between two paragraphs of a freelance pricing article and offers that article’s checklist as a PDF, with a promise of one email and nothing else. Five-day email course pairs a dark panel listing the five lessons with a sign-up for a sourdough course. In each, the email field asks only for the resource; joining the regular newsletter is a separate checkbox marked optional that starts unticked, which keeps the consent clear. The copy says exactly what arrives and when. These are inline alternatives to exit-intent popups: they sit in the content where the offer is relevant. A vanilla script validates the address, writes the error into the linked message, and swaps in a confirmation that names the address. Nothing is sent.
What’s included
Book cover, course outline and in-article box, all drawn with CSS
Optional first name with autocomplete="given-name"
Separate, unticked newsletter opt-in checkbox
Clear promise of what arrives and how often
Bootstrap version uses .card, .form-check, .list-unstyled and the grid row
Accessibility
Every input has a visible label and an autocomplete value; optional fields say so in the label. Hints and errors are linked with aria-describedby, invalid input gets aria-invalid and focus, and the confirmation is focused when it appears. The in-article box is an aside named by its heading, decorative covers and icons are aria-hidden, focus rings are visible and transitions switch off under prefers-reduced-motion.
Customise it
Change --lm-accent, --lm-accent-dark and --lm-ring on .lm for buttons and focus, and --lm-gold for the cover and course highlights; the cover artwork is the .lm-cover background. In Tailwind edit the lm-* colours and the lm-cover image in @theme.