Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="nc-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
  <symbol id="nc-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 3v5h5M9 13h6M9 17h4"/></symbol>
  <symbol id="nc-i-cal" 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="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></symbol>
  <symbol id="nc-i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20c.6-3.6 3.2-5.5 6.5-5.5s5.9 1.9 6.5 5.5M16 4.6a3.5 3.5 0 0 1 0 6.8M18.5 14.8c1.7.8 2.7 2.5 3 5.2"/></symbol>
</svg>

<div class="demo-grid">
<div><span class="lbl">Issue card with stats and sample</span>
<section class="nc nc-card" aria-labelledby="lf-title">
  <div class="nc-cover">
    <span class="nc-issue">Issue 148 · 11 Sep 2026</span>
    <p class="nc-latest"><small>Latest issue</small>Why hedgerows are coming back to the Weald</p>
  </div>
  <div class="nc-body">
    <h3 class="nc-name" id="lf-title">The Long Field</h3>
    <p class="nc-desc">A fortnightly letter about regenerative farming: what's working on real farms, with the soil tests to prove it.</p>
    <dl class="nc-stats">
      <div><dt>Issues</dt><dd>148</dd></div>
      <div><dt>Readers</dt><dd>18,400</dd></div>
      <div><dt>Arrives</dt><dd>Thursdays</dd></div>
    </dl>
    <form class="nc-form" action="#" method="post" data-nf="lf-done">
      <label class="nc-label" for="lf-email">Email address</label>
      <div class="nc-row">
        <input class="nc-input" id="lf-email" name="email" type="email" autocomplete="email" required aria-describedby="lf-email-err lf-hint" data-err="lf-email-err"
          data-missing="Enter your email address to subscribe." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="nc-btn" type="submit">Subscribe free</button>
      </div>
      <p class="nc-err" id="lf-email-err"></p>
      <p class="nc-hint" id="lf-hint">Every other Thursday. Unsubscribe in one click.</p>
    </form>
    <div class="nc-done" id="lf-done" tabindex="-1" hidden>
      <span class="nc-tick" aria-hidden="true"><svg><use href="#nc-i-tick"/></svg></span>
      <div><b>Confirm your subscription</b><p>We sent a link to <span data-email>your inbox</span>. Issue 149 goes out on 25 September.</p></div>
    </div>
    <a class="nc-sample" href="#sample-issue"><svg aria-hidden="true"><use href="#nc-i-doc"/></svg>Read a sample issue</a>
  </div>
</section>
</div>

<div><span class="lbl">Author card with last issue</span>
<section class="nc nc-author" aria-labelledby="us-title">
  <div class="nc-who">
    <span class="nc-avatar" aria-hidden="true">RL</span>
    <div><b>Rosa Lindqvist</b><span>Urban ecologist, London</span></div>
  </div>
  <h3 id="us-title">Understory</h3>
  <p class="nc-desc">The trees on your street, who planted them and what they are up to. Short, illustrated, every Sunday.</p>
  <ul class="nc-chips" aria-label="About this newsletter">
    <li>212 issues</li><li>9,870 subscribers</li><li>Free</li>
  </ul>
  <form class="nc-form" action="#" method="post" data-nf="us-done">
    <label class="nc-label" for="us-email">Your email</label>
    <div class="nc-row">
      <input class="nc-input" id="us-email" name="email" type="email" autocomplete="email" required aria-describedby="us-email-err us-hint" data-err="us-email-err"
        data-missing="Enter your email address to subscribe." data-bad="Enter an email address with an @ and a domain, like [email protected].">
      <button class="nc-btn" type="submit">Subscribe</button>
    </div>
    <p class="nc-err" id="us-email-err"></p>
    <p class="nc-hint" id="us-hint">Your address stays with Rosa. No ads, no sharing.</p>
  </form>
  <div class="nc-done" id="us-done" tabindex="-1" hidden>
    <span class="nc-tick" aria-hidden="true"><svg><use href="#nc-i-tick"/></svg></span>
    <div><b>One more step</b><p>Check <span data-email>your inbox</span> for a confirmation link from Rosa.</p></div>
  </div>
  <a class="nc-last" href="#last-issue">Last week: The plane trees of Pimlico, and why they shed their bark</a>
</section>
</div>
</div>

<div><span class="lbl">Footer with a newsletter column</span>
<footer class="nc nc-footer">
  <div class="nc-foot-grid">
    <div class="nc-foot-news">
      <p class="nc-brand">Brasswick Kitchen Supply</p>
      <p>Cast iron, carbon steel and the know-how to look after it.</p>
      <form class="nc-form" action="#" method="post" data-nf="bk-done">
        <label class="nc-label" for="bk-email">Get recipes and restock alerts</label>
        <div class="nc-row">
          <input class="nc-input" id="bk-email" name="email" type="email" autocomplete="email" required aria-describedby="bk-email-err bk-hint" data-err="bk-email-err"
            data-missing="Enter your email address to sign up." data-bad="Enter an email address with an @ and a domain, like [email protected].">
          <button class="nc-btn" type="submit">Sign up</button>
        </div>
        <p class="nc-err" id="bk-email-err"></p>
        <p class="nc-hint" id="bk-hint">Two emails a month. Read our <a href="#privacy">privacy policy</a>.</p>
        <p class="nc-meta"><span><svg aria-hidden="true"><use href="#nc-i-users"/></svg>31,200 cooks</span><span><svg aria-hidden="true"><use href="#nc-i-cal"/></svg>Issue 87 out 1 October</span></p>
      </form>
      <div class="nc-done" id="bk-done" tabindex="-1" hidden>
        <span class="nc-tick" aria-hidden="true"><svg><use href="#nc-i-tick"/></svg></span>
        <div><b>Almost done</b><p>Confirm from the email we sent to <span data-email>you</span> and the October issue is yours.</p></div>
      </div>
    </div>
    <nav class="nc-col" aria-label="Shop"><h3>Shop</h3><ul><li><a href="#skillets">Skillets</a></li><li><a href="#dutch-ovens">Dutch ovens</a></li><li><a href="#woks">Carbon steel woks</a></li><li><a href="#care">Care kits</a></li></ul></nav>
    <nav class="nc-col" aria-label="Learn"><h3>Learn</h3><ul><li><a href="#seasoning">Seasoning guide</a></li><li><a href="#recipes">Recipes</a></li><li><a href="#journal">Journal</a></li></ul></nav>
    <nav class="nc-col" aria-label="Help"><h3>Help</h3><ul><li><a href="#delivery">Delivery</a></li><li><a href="#returns">Returns</a></li><li><a href="#warranty">Lifetime warranty</a></li><li><a href="#contact">Contact</a></li></ul></nav>
  </div>
  <div class="nc-bottom"><span>© 2026 Brasswick Kitchen Supply Ltd, Sheffield</span><span><a href="#terms">Terms</a> · <a href="#privacy">Privacy</a></span></div>
</footer>
</div>
CSS
/* Newsletter cards and a footer signup. Each one sells the newsletter before asking for anything: issue count,
   reader count, how often it arrives and a link to a sample issue. One labelled email field, errors tied to the
   input with aria-describedby, and a success state that replaces the form. */
.nc{--nc-ink:#18231d;--nc-muted:#4f5b53;--nc-line:#7d8a80;--nc-accent:#1f6f50;--nc-accent-dark:#165239;--nc-ring:rgba(31,111,80,.24);
  --nc-error:#b42318;--nc-soft:#eef4ef;--nc-serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;color:var(--nc-ink);font-size:.92rem;line-height:1.5}
.nc a{color:var(--nc-accent-dark);font-weight:600;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
.nc a:hover{text-decoration-thickness:2px}
.nc a:focus-visible{outline:2px solid var(--nc-accent);outline-offset:2px;border-radius:3px}
.nc-label{display:block;font-size:.84rem;font-weight:650;margin-bottom:6px}
.nc-row{display:flex;gap:8px}
.nc-input{display:block;flex:1 1 0;width:100%;min-width:0;font:inherit;color:var(--nc-ink);background:#fff;border:1.5px solid var(--nc-line);border-radius:10px;padding:10px 13px;
  transition:border-color .15s,box-shadow .15s}
.nc-input:focus{outline:none;border-color:var(--nc-accent);box-shadow:0 0 0 4px var(--nc-ring)}
.nc-input.is-invalid{border-color:var(--nc-error)}
.nc-btn{flex:none;font:inherit;font-weight:650;color:#fff;background:var(--nc-accent);border:0;border-radius:10px;padding:10px 18px;cursor:pointer;transition:background-color .15s}
.nc-btn:hover{background:var(--nc-accent-dark)}
.nc-btn:focus-visible{outline:2px solid var(--nc-accent);outline-offset:3px}
.nc-err{display:flex;gap:7px;align-items:flex-start;font-size:.82rem;font-weight:600;color:var(--nc-error);margin-top:7px}
.nc-err:empty{display:none}
.nc-err::before{content:"!";flex:none;display:grid;place-items:center;width:16px;height:16px;margin-top:2px;border-radius:50%;background:var(--nc-error);color:#fff;font-size:.7rem;font-weight:800}
.nc-hint{font-size:.8rem;color:var(--nc-muted);margin-top:8px}
.nc-done{outline:none;display:flex;gap:12px;align-items:flex-start;background:var(--nc-soft);border-radius:12px;padding:14px 16px}
.nc-done[hidden],.nc-form[hidden]{display:none}
.nc-done:focus-visible{outline:2px solid var(--nc-accent);outline-offset:3px}
.nc-tick{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--nc-accent);color:#fff}
.nc-tick svg{width:16px;height:16px}
.nc-done b{display:block;font-size:.95rem}
.nc-done p{color:var(--nc-muted);font-size:.86rem}
.nc-done span[data-email]{color:var(--nc-ink);font-weight:600;overflow-wrap:anywhere}

/* 1. Issue card: latest issue on a cover, stats, form, sample link */
.nc-card{background:#fff;border-radius:20px;overflow:hidden;box-shadow:0 1px 2px rgba(24,35,29,.06),0 20px 44px -24px rgba(24,35,29,.35)}
.nc-cover{position:relative;height:150px;padding:18px 20px;display:flex;flex-direction:column;justify-content:space-between;align-items:flex-start;
  background:linear-gradient(180deg,#f3d58f 0 34%,#e8bf6a 34% 40%,transparent 40%),repeating-linear-gradient(168deg,#4f7d42 0 9px,#5f8d4f 9px 18px)}
.nc-cover::after{content:"";position:absolute;right:30px;top:20px;width:34px;height:34px;border-radius:50%;background:#fff6d8;box-shadow:0 0 0 8px rgba(255,246,216,.35)}
.nc-issue{position:relative;z-index:1;font-size:.74rem;font-weight:700;color:var(--nc-ink);background:#fff;border-radius:999px;padding:3px 10px}
.nc-latest{position:relative;z-index:1;background:rgba(255,255,255,.95);border-radius:10px;padding:8px 12px;font-size:.82rem;line-height:1.35;max-width:88%}
.nc-latest small{display:block;font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--nc-muted)}
.nc-body{padding:22px 24px 24px}
.nc-name{font-family:var(--nc-serif);font-size:1.5rem;font-weight:700;letter-spacing:-.01em;line-height:1.2}
.nc-desc{color:var(--nc-muted);margin:6px 0 16px}
.nc-stats{display:grid;grid-template-columns:repeat(3,1fr);margin:0 0 18px;border:1px solid #dfe5df;border-radius:12px}
.nc-stats div{padding:9px 12px}
.nc-stats div+div{border-left:1px solid #dfe5df}
.nc-stats dt{font-size:.72rem;color:var(--nc-muted)}
.nc-stats dd{font-weight:700;font-size:.98rem}
.nc-sample{display:inline-flex;align-items:center;gap:6px;margin-top:14px;font-size:.86rem}
.nc-sample svg{width:16px;height:16px}

/* 2. Author card: the writer, the promise, the last issue */
.nc-author{background:#fff;border-radius:20px;padding:26px 24px 24px;box-shadow:0 1px 2px rgba(24,35,29,.06),0 20px 44px -24px rgba(24,35,29,.35)}
.nc-who{display:flex;align-items:center;gap:14px;margin-bottom:14px}
.nc-avatar{flex:none;display:grid;place-items:center;width:56px;height:56px;border-radius:50%;font-family:var(--nc-serif);font-size:1.3rem;font-weight:700;color:#fff;
  background:linear-gradient(140deg,#2f7d5a,#14432f);box-shadow:0 0 0 3px #fff,0 0 0 5px #cfe3d6}
.nc-who b{display:block;font-size:1rem}
.nc-who div span{font-size:.82rem;color:var(--nc-muted)}
.nc-author h3{font-family:var(--nc-serif);font-size:1.35rem;font-weight:700;line-height:1.25;letter-spacing:-.01em}
.nc-author .nc-desc{margin-bottom:14px}
.nc-chips{display:flex;flex-wrap:wrap;gap:6px;list-style:none;margin-bottom:18px}
.nc-chips li{font-size:.76rem;font-weight:600;color:#1e4d38;background:var(--nc-soft);border:1px solid #d3e3d8;border-radius:999px;padding:3px 10px}
.nc-last{display:block;margin-top:16px;padding-top:14px;border-top:1px dashed #cdd6cf;font-size:.84rem;color:var(--nc-muted)}

/* 3. Footer with a newsletter column */
.nc-footer{background:#f8f6ef;border:1px solid #e1ddd0;border-radius:22px;padding:34px 34px 22px}
.nc-foot-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:28px}
.nc-brand{font-family:var(--nc-serif);font-size:1.25rem;font-weight:700}
.nc-brand+p{color:var(--nc-muted);margin-top:6px;font-size:.86rem}
.nc-foot-news{min-width:0}
.nc-foot-news .nc-form{margin-top:18px}
.nc-foot-news .nc-meta{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:10px;font-size:.78rem;color:var(--nc-muted)}
.nc-foot-news .nc-meta span{display:inline-flex;align-items:center;gap:5px}
.nc-foot-news .nc-meta svg{width:14px;height:14px}
.nc-col h3{font-size:.74rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--nc-muted);margin-bottom:10px}
.nc-col ul{list-style:none;display:grid;gap:7px}
.nc-col a{color:var(--nc-ink);font-weight:500;text-decoration:none}
.nc-col a:hover{text-decoration:underline}
.nc-bottom{display:flex;flex-wrap:wrap;justify-content:space-between;gap:10px;margin-top:30px;padding-top:16px;border-top:1px solid #e1ddd0;font-size:.8rem;color:var(--nc-muted)}
.nc-bottom a{color:var(--nc-muted);font-weight:500}

@media (max-width:760px){.nc-foot-grid{grid-template-columns:1fr 1fr 1fr}.nc-foot-news{grid-column:1/-1;grid-row:auto}}
@media (max-width:420px){.nc-row{flex-direction:column}.nc-body{padding:20px 18px 22px}.nc-author{padding:22px 18px}.nc-footer{padding:26px 20px 18px}
  .nc-foot-grid{grid-template-columns:1fr 1fr}.nc-stats div{padding:8px 9px}}
@media (prefers-reduced-motion:reduce){.nc-input,.nc-btn{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 = '';
    if (el.tagName === 'FIELDSET') { if (!el.querySelector('input:checked')) msg = el.getAttribute('data-missing'); }
    else if (!el.checkValidity()) msg = 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 (el.tagName !== 'FIELDSET') { 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.tagName === 'FIELDSET' ? first.querySelector('input') : first).focus(); return; }
      if (!done) return;
      done.querySelectorAll('[data-email]').forEach(function (n) { n.textContent = email.value; });
      form.hidden = true; done.hidden = false; done.focus();
    });
    // once a field shows an error, re-check it while it is being fixed
    ['input', 'change'].forEach(function (type) {
      form.addEventListener(type, function (e) { var el = e.target.closest('.is-invalid'); if (el) check(el); });
    });
    var back = done && done.querySelector('[data-nf-back]');
    if (back) back.addEventListener('click', function () { done.hidden = true; form.hidden = false; email.focus(); });
  });
})();

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="nc-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
  <symbol id="nc-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 3v5h5M9 13h6M9 17h4"/></symbol>
  <symbol id="nc-i-cal" 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="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></symbol>
  <symbol id="nc-i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20c.6-3.6 3.2-5.5 6.5-5.5s5.9 1.9 6.5 5.5M16 4.6a3.5 3.5 0 0 1 0 6.8M18.5 14.8c1.7.8 2.7 2.5 3 5.2"/></symbol>
</svg>

<div class="demo-grid">
<div><span class="lbl">Issue card with stats and sample</span>
<section class="card nc nc-issue-card" aria-labelledby="lf-title">
  <div class="nc-cover">
    <span class="badge rounded-pill">Issue 148 · 11 Sep 2026</span>
    <p class="nc-latest"><small>Latest issue</small>Why hedgerows are coming back to the Weald</p>
  </div>
  <div class="card-body">
    <h3 class="nc-name" id="lf-title">The Long Field</h3>
    <p class="nc-desc">A fortnightly letter about regenerative farming: what's working on real farms, with the soil tests to prove it.</p>
    <dl class="nc-stats">
      <div><dt>Issues</dt><dd>148</dd></div>
      <div><dt>Readers</dt><dd>18,400</dd></div>
      <div><dt>Arrives</dt><dd>Thursdays</dd></div>
    </dl>
    <form action="#" method="post" data-nf="lf-done">
      <label class="form-label" for="lf-email">Email address</label>
      <div class="d-flex gap-2 nc-row">
        <input class="form-control" id="lf-email" name="email" type="email" autocomplete="email" required aria-describedby="lf-email-err lf-hint" data-err="lf-email-err"
          data-missing="Enter your email address to subscribe." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="btn nc-btn" type="submit">Subscribe free</button>
      </div>
      <p class="nc-err" id="lf-email-err"></p>
      <p class="form-text" id="lf-hint">Every other Thursday. Unsubscribe in one click.</p>
    </form>
    <div class="nc-done" id="lf-done" tabindex="-1" hidden>
      <span class="nc-tick" aria-hidden="true"><svg><use href="#nc-i-tick"/></svg></span>
      <div><b>Confirm your subscription</b><p>We sent a link to <span data-email>your inbox</span>. Issue 149 goes out on 25 September.</p></div>
    </div>
    <a class="nc-sample" href="#sample-issue"><svg aria-hidden="true"><use href="#nc-i-doc"/></svg>Read a sample issue</a>
  </div>
</section>
</div>

<div><span class="lbl">Author card with last issue</span>
<section class="card nc nc-author" aria-labelledby="us-title">
  <div class="card-body">
    <div class="nc-who">
      <span class="nc-avatar" aria-hidden="true">RL</span>
      <div><b>Rosa Lindqvist</b><span>Urban ecologist, London</span></div>
    </div>
    <h3 id="us-title">Understory</h3>
    <p class="nc-desc">The trees on your street, who planted them and what they are up to. Short, illustrated, every Sunday.</p>
    <ul class="list-unstyled d-flex flex-wrap gap-2 nc-chips" aria-label="About this newsletter">
      <li class="badge rounded-pill">212 issues</li><li class="badge rounded-pill">9,870 subscribers</li><li class="badge rounded-pill">Free</li>
    </ul>
    <form action="#" method="post" data-nf="us-done">
      <label class="form-label" for="us-email">Your email</label>
      <div class="d-flex gap-2 nc-row">
        <input class="form-control" id="us-email" name="email" type="email" autocomplete="email" required aria-describedby="us-email-err us-hint" data-err="us-email-err"
          data-missing="Enter your email address to subscribe." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="btn nc-btn" type="submit">Subscribe</button>
      </div>
      <p class="nc-err" id="us-email-err"></p>
      <p class="form-text" id="us-hint">Your address stays with Rosa. No ads, no sharing.</p>
    </form>
    <div class="nc-done" id="us-done" tabindex="-1" hidden>
      <span class="nc-tick" aria-hidden="true"><svg><use href="#nc-i-tick"/></svg></span>
      <div><b>One more step</b><p>Check <span data-email>your inbox</span> for a confirmation link from Rosa.</p></div>
    </div>
    <a class="nc-last" href="#last-issue">Last week: The plane trees of Pimlico, and why they shed their bark</a>
  </div>
</section>
</div>
</div>

<div><span class="lbl">Footer with a newsletter column</span>
<footer class="nc nc-footer">
  <div class="row">
    <div class="col-12 col-md-5">
      <p class="nc-brand">Brasswick Kitchen Supply</p>
      <p>Cast iron, carbon steel and the know-how to look after it.</p>
      <form action="#" method="post" data-nf="bk-done">
        <label class="form-label" for="bk-email">Get recipes and restock alerts</label>
        <div class="d-flex gap-2 nc-row">
          <input class="form-control" id="bk-email" name="email" type="email" autocomplete="email" required aria-describedby="bk-email-err bk-hint" data-err="bk-email-err"
            data-missing="Enter your email address to sign up." data-bad="Enter an email address with an @ and a domain, like [email protected].">
          <button class="btn nc-btn" type="submit">Sign up</button>
        </div>
        <p class="nc-err" id="bk-email-err"></p>
        <p class="form-text" id="bk-hint">Two emails a month. Read our <a href="#privacy">privacy policy</a>.</p>
        <p class="nc-meta"><span><svg aria-hidden="true"><use href="#nc-i-users"/></svg>31,200 cooks</span><span><svg aria-hidden="true"><use href="#nc-i-cal"/></svg>Issue 87 out 1 October</span></p>
      </form>
      <div class="nc-done" id="bk-done" tabindex="-1" hidden>
        <span class="nc-tick" aria-hidden="true"><svg><use href="#nc-i-tick"/></svg></span>
        <div><b>Almost done</b><p>Confirm from the email we sent to <span data-email>you</span> and the October issue is yours.</p></div>
      </div>
    </div>
    <nav class="col-6 col-sm-4 col-md nc-col" aria-label="Shop"><h3>Shop</h3><ul class="list-unstyled"><li><a href="#skillets">Skillets</a></li><li><a href="#dutch-ovens">Dutch ovens</a></li><li><a href="#woks">Carbon steel woks</a></li><li><a href="#care">Care kits</a></li></ul></nav>
    <nav class="col-6 col-sm-4 col-md nc-col" aria-label="Learn"><h3>Learn</h3><ul class="list-unstyled"><li><a href="#seasoning">Seasoning guide</a></li><li><a href="#recipes">Recipes</a></li><li><a href="#journal">Journal</a></li></ul></nav>
    <nav class="col-6 col-sm-4 col-md nc-col" aria-label="Help"><h3>Help</h3><ul class="list-unstyled"><li><a href="#delivery">Delivery</a></li><li><a href="#returns">Returns</a></li><li><a href="#warranty">Lifetime warranty</a></li><li><a href="#contact">Contact</a></li></ul></nav>
  </div>
  <div class="nc-bottom"><span>© 2026 Brasswick Kitchen Supply Ltd, Sheffield</span><span><a href="#terms">Terms</a> · <a href="#privacy">Privacy</a></span></div>
</footer>
</div>
CSS
/* Bootstrap .card, .form-label, .form-control, .btn, .form-text, .badge and the grid row for the footer,
   recoloured through Bootstrap's CSS variables. Errors are tied to the input with aria-describedby. */
.nc{--nc-ink:#18231d;--nc-muted:#4f5b53;--nc-line:#7d8a80;--nc-accent:#1f6f50;--nc-accent-dark:#165239;--nc-ring:rgba(31,111,80,.24);
  --nc-error:#b42318;--nc-soft:#eef4ef;--nc-serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;color:var(--nc-ink);font-size:.92rem;line-height:1.5}
.nc p,.nc h3,.nc dl,.nc dd,.nc ul{margin:0}
.nc a{color:var(--nc-accent-dark);font-weight:600;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
.nc a:hover{text-decoration-thickness:2px}
.nc a:focus-visible{outline:2px solid var(--nc-accent);outline-offset:2px;border-radius:3px}
.nc .form-label{display:block;font-size:.84rem;font-weight:650;margin-bottom:6px}
.nc .form-control{flex:1 1 0;min-width:0;font-size:inherit;line-height:inherit;color:var(--nc-ink);border:1.5px solid var(--nc-line);border-radius:10px;padding:10px 13px;
  transition:border-color .15s,box-shadow .15s}
.nc .form-control:focus{border-color:var(--nc-accent);box-shadow:0 0 0 4px var(--nc-ring)}
.nc .form-control.is-invalid{border-color:var(--nc-error);background-image:none;padding-right:13px}
.nc .nc-btn{--bs-btn-color:#fff;--bs-btn-bg:var(--nc-accent);--bs-btn-border-color:var(--nc-accent);--bs-btn-hover-color:#fff;--bs-btn-hover-bg:var(--nc-accent-dark);
  --bs-btn-hover-border-color:var(--nc-accent-dark);--bs-btn-active-color:#fff;--bs-btn-active-bg:var(--nc-accent-dark);--bs-btn-active-border-color:var(--nc-accent-dark);
  --bs-btn-focus-box-shadow:none;--bs-btn-font-weight:650;--bs-btn-font-size:.92rem;--bs-btn-line-height:1.5;--bs-btn-padding-x:18px;--bs-btn-padding-y:10px;
  --bs-btn-border-width:0;--bs-btn-border-radius:10px;flex:none;transition:background-color .15s}
.nc .nc-btn:focus-visible{outline:2px solid var(--nc-accent);outline-offset:3px}
.nc .nc-err{display:flex;gap:7px;align-items:flex-start;font-size:.82rem;font-weight:600;color:var(--nc-error);margin-top:7px}
.nc .nc-err:empty{display:none}
.nc .nc-err::before{content:"!";flex:none;display:grid;place-items:center;width:16px;height:16px;margin-top:2px;border-radius:50%;background:var(--nc-error);color:#fff;font-size:.7rem;font-weight:800}
.nc .form-text{font-size:.8rem;color:var(--nc-muted);margin-top:8px}
.nc .nc-done{outline:none;display:flex;gap:12px;align-items:flex-start;background:var(--nc-soft);border-radius:12px;padding:14px 16px}
.nc .nc-done[hidden],.nc form[hidden]{display:none}
.nc .nc-done:focus-visible{outline:2px solid var(--nc-accent);outline-offset:3px}
.nc .nc-tick{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--nc-accent);color:#fff}
.nc .nc-tick svg{width:16px;height:16px}
.nc .nc-done b{display:block;font-size:.95rem}
.nc .nc-done p{color:var(--nc-muted);font-size:.86rem}
.nc .nc-done span[data-email]{color:var(--nc-ink);font-weight:600;overflow-wrap:anywhere}

/* 1. Issue card */
.nc.card{--bs-card-border-width:0;--bs-card-border-radius:20px;--bs-card-spacer-x:24px;--bs-card-spacer-y:22px;overflow:hidden;box-shadow:0 1px 2px rgba(24,35,29,.06),0 20px 44px -24px rgba(24,35,29,.35)}
.nc-cover{position:relative;height:150px;padding:18px 20px;display:flex;flex-direction:column;justify-content:space-between;align-items:flex-start;
  background:linear-gradient(180deg,#f3d58f 0 34%,#e8bf6a 34% 40%,transparent 40%),repeating-linear-gradient(168deg,#4f7d42 0 9px,#5f8d4f 9px 18px)}
.nc-cover::after{content:"";position:absolute;right:30px;top:20px;width:34px;height:34px;border-radius:50%;background:#fff6d8;box-shadow:0 0 0 8px rgba(255,246,216,.35)}
.nc-cover .badge{position:relative;z-index:1;--bs-badge-font-size:.74rem;--bs-badge-font-weight:700;--bs-badge-padding-x:10px;--bs-badge-padding-y:3px;--bs-badge-color:var(--nc-ink);background:#fff;line-height:1.5}
.nc-latest{position:relative;z-index:1;background:rgba(255,255,255,.95);border-radius:10px;padding:8px 12px;font-size:.82rem;line-height:1.35;max-width:88%}
.nc-latest small{display:block;font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--nc-muted)}
.nc-issue-card .card-body{padding-bottom:24px}
.nc-name{font-family:var(--nc-serif);font-size:1.5rem;font-weight:700;letter-spacing:-.01em;line-height:1.2}
.nc .nc-desc{color:var(--nc-muted);margin:6px 0 16px}
.nc-stats{display:grid;grid-template-columns:repeat(3,1fr);margin:0 0 18px!important;border:1px solid #dfe5df;border-radius:12px}
.nc-stats div{padding:9px 12px}
.nc-stats div+div{border-left:1px solid #dfe5df}
.nc-stats dt{font-size:.72rem;font-weight:400;color:var(--nc-muted)}
.nc-stats dd{font-weight:700;font-size:.98rem}
.nc .nc-sample{display:inline-flex;align-items:center;gap:6px;margin-top:14px;font-size:.86rem}
.nc-sample svg{width:16px;height:16px}

/* 2. Author card */
.nc-author .card-body{padding:26px 24px 24px}
.nc-who{display:flex;align-items:center;gap:14px;margin-bottom:14px}
.nc-avatar{flex:none;display:grid;place-items:center;width:56px;height:56px;border-radius:50%;font-family:var(--nc-serif);font-size:1.3rem;font-weight:700;color:#fff;
  background:linear-gradient(140deg,#2f7d5a,#14432f);box-shadow:0 0 0 3px #fff,0 0 0 5px #cfe3d6}
.nc-who b{display:block;font-size:1rem}
.nc-who div span{font-size:.82rem;color:var(--nc-muted)}
.nc-author h3{font-family:var(--nc-serif);font-size:1.35rem;font-weight:700;line-height:1.25;letter-spacing:-.01em}
.nc-author .nc-desc{margin-bottom:14px}
.nc-chips{margin-bottom:18px!important}
.nc-chips .badge{--bs-badge-font-size:.76rem;--bs-badge-font-weight:600;--bs-badge-padding-x:10px;--bs-badge-padding-y:3px;--bs-badge-color:#1e4d38;background:var(--nc-soft);border:1px solid #d3e3d8;line-height:1.5}
.nc .nc-last{display:block;margin-top:16px;padding-top:14px;border-top:1px dashed #cdd6cf;font-size:.84rem}

/* 3. Footer with a newsletter column */
.nc-footer{background:#f8f6ef;border:1px solid #e1ddd0;border-radius:22px;padding:34px 34px 22px}
.nc-footer .row{--bs-gutter-x:28px;--bs-gutter-y:28px}
.nc-brand{font-family:var(--nc-serif);font-size:1.25rem;font-weight:700}
.nc .nc-brand+p{color:var(--nc-muted);margin-top:6px;font-size:.86rem}
.nc-footer form{margin-top:18px}
.nc .nc-meta{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:10px;font-size:.78rem;color:var(--nc-muted)}
.nc-meta span{display:inline-flex;align-items:center;gap:5px}
.nc-meta svg{width:14px;height:14px}
.nc-col h3{font-size:.74rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--nc-muted);margin-bottom:10px}
.nc-col ul{display:grid;gap:7px}
.nc .nc-col a{color:var(--nc-ink);font-weight:500;text-decoration:none}
.nc .nc-col a:hover{text-decoration:underline}
.nc-bottom{display:flex;flex-wrap:wrap;justify-content:space-between;gap:10px;margin-top:30px;padding-top:16px;border-top:1px solid #e1ddd0;font-size:.8rem;color:var(--nc-muted)}
.nc .nc-bottom a{color:var(--nc-muted);font-weight:500}

@media (max-width:420px){.nc .nc-row{flex-direction:column}.nc-issue-card .card-body{padding:20px 18px 22px}.nc-author .card-body{padding:22px 18px}.nc-footer{padding:26px 20px 18px}.nc-stats div{padding:8px 9px}}
@media (prefers-reduced-motion:reduce){.nc .form-control,.nc .nc-btn{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 = '';
    if (el.tagName === 'FIELDSET') { if (!el.querySelector('input:checked')) msg = el.getAttribute('data-missing'); }
    else if (!el.checkValidity()) msg = 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 (el.tagName !== 'FIELDSET') { 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.tagName === 'FIELDSET' ? first.querySelector('input') : first).focus(); return; }
      if (!done) return;
      done.querySelectorAll('[data-email]').forEach(function (n) { n.textContent = email.value; });
      form.hidden = true; done.hidden = false; done.focus();
    });
    // once a field shows an error, re-check it while it is being fixed
    ['input', 'change'].forEach(function (type) {
      form.addEventListener(type, function (e) { var el = e.target.closest('.is-invalid'); if (el) check(el); });
    });
    var back = done && done.querySelector('[data-nf-back]');
    if (back) back.addEventListener('click', function () { done.hidden = true; form.hidden = false; email.focus(); });
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="nc-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
  <symbol id="nc-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 3v5h5M9 13h6M9 17h4"/></symbol>
  <symbol id="nc-i-cal" 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="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></symbol>
  <symbol id="nc-i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20c.6-3.6 3.2-5.5 6.5-5.5s5.9 1.9 6.5 5.5M16 4.6a3.5 3.5 0 0 1 0 6.8M18.5 14.8c1.7.8 2.7 2.5 3 5.2"/></symbol>
</svg>

<div class="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] items-start gap-[34px]">
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#56604f] uppercase">Issue card with stats and sample</span>
<section class="overflow-hidden rounded-[20px] bg-white text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(24,35,29,.06),0_20px_44px_-24px_rgba(24,35,29,.35)]" aria-labelledby="lf-title">
  <div class="relative flex h-[150px] flex-col items-start justify-between bg-[linear-gradient(180deg,#f3d58f_0_34%,#e8bf6a_34%_40%,transparent_40%),repeating-linear-gradient(168deg,#4f7d42_0_9px,#5f8d4f_9px_18px)] px-5 py-[18px] after:absolute after:top-5 after:right-[30px] after:size-[34px] after:rounded-full after:bg-[#fff6d8] after:shadow-[0_0_0_8px_rgba(255,246,216,.35)] after:content-['']">
    <span class="relative z-1 rounded-full bg-white px-2.5 py-[3px] text-[.74rem] font-bold">Issue 148 · 11 Sep 2026</span>
    <p class="relative z-1 max-w-[88%] rounded-[10px] bg-white/95 px-3 py-2 text-[.82rem] leading-[1.35]"><small class="block text-[.7rem] font-bold tracking-[.06em] text-nc-muted uppercase">Latest issue</small>Why hedgerows are coming back to the Weald</p>
  </div>
  <div class="px-6 pt-[22px] pb-6 max-[420px]:px-[18px] max-[420px]:pt-5 max-[420px]:pb-[22px]">
    <h3 class="font-nc-serif text-[1.5rem] leading-[1.2] font-bold tracking-[-.01em]" id="lf-title">The Long Field</h3>
    <p class="mt-1.5 mb-4 text-nc-muted">A fortnightly letter about regenerative farming: what's working on real farms, with the soil tests to prove it.</p>
    <dl class="mb-[18px] grid grid-cols-3 divide-x divide-[#dfe5df] rounded-xl border border-[#dfe5df]">
      <div class="px-3 py-[9px] max-[420px]:px-[9px] max-[420px]:py-2"><dt class="text-[.72rem] text-nc-muted">Issues</dt><dd class="text-[.98rem] font-bold">148</dd></div>
      <div class="px-3 py-[9px] max-[420px]:px-[9px] max-[420px]:py-2"><dt class="text-[.72rem] text-nc-muted">Readers</dt><dd class="text-[.98rem] font-bold">18,400</dd></div>
      <div class="px-3 py-[9px] max-[420px]:px-[9px] max-[420px]:py-2"><dt class="text-[.72rem] text-nc-muted">Arrives</dt><dd class="text-[.98rem] font-bold">Thursdays</dd></div>
    </dl>
    <form action="#" method="post" data-nf="lf-done">
      <label class="mb-1.5 block text-[.84rem] font-[650]" for="lf-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-nc-line bg-white px-[13px] py-2.5 text-nc-ink transition-[border-color,box-shadow] duration-150 focus:border-nc-accent focus:shadow-[0_0_0_4px_rgba(31,111,80,.24)] focus:outline-none [&.is-invalid]:border-nc-error motion-reduce:transition-none max-[420px]:basis-auto" id="lf-email" name="email" type="email" autocomplete="email" required aria-describedby="lf-email-err lf-hint" data-err="lf-email-err"
          data-missing="Enter your email address to subscribe." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="flex-none cursor-pointer rounded-[10px] bg-nc-accent px-[18px] py-2.5 font-[650] text-white transition-colors duration-150 hover:bg-nc-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-nc-accent motion-reduce:transition-none" type="submit">Subscribe free</button>
      </div>
      <p class="mt-[7px] flex items-start gap-[7px] text-[.82rem] font-semibold text-nc-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-nc-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="lf-email-err"></p>
      <p class="mt-2 text-[.8rem] text-nc-muted" id="lf-hint">Every other Thursday. Unsubscribe in one click.</p>
    </form>
    <div class="flex items-start gap-3 rounded-xl bg-nc-soft px-4 py-3.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-nc-accent [&[hidden]]:hidden" id="lf-done" tabindex="-1" hidden>
      <span class="grid size-8 flex-none place-items-center rounded-full bg-nc-accent text-white" aria-hidden="true"><svg class="size-4"><use href="#nc-i-tick"/></svg></span>
      <div><b class="block text-[.95rem]">Confirm your subscription</b><p class="text-[.86rem] text-nc-muted">We sent a link to <span class="font-semibold wrap-anywhere text-nc-ink" data-email>your inbox</span>. Issue 149 goes out on 25 September.</p></div>
    </div>
    <a class="mt-3.5 inline-flex items-center gap-1.5 text-[.86rem] font-semibold text-nc-accent-dark underline decoration-1 underline-offset-3 hover:decoration-2 focus-visible:rounded-[3px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nc-accent" href="#sample-issue"><svg class="size-4" aria-hidden="true"><use href="#nc-i-doc"/></svg>Read a sample issue</a>
  </div>
</section>
</div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#56604f] uppercase">Author card with last issue</span>
<section class="rounded-[20px] bg-white px-6 pt-[26px] pb-6 text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(24,35,29,.06),0_20px_44px_-24px_rgba(24,35,29,.35)] max-[420px]:px-[18px] max-[420px]:py-[22px]" aria-labelledby="us-title">
  <div class="mb-3.5 flex items-center gap-3.5">
    <span class="grid size-14 flex-none place-items-center rounded-full bg-[linear-gradient(140deg,#2f7d5a,#14432f)] font-nc-serif text-[1.3rem] font-bold text-white shadow-[0_0_0_3px_#fff,0_0_0_5px_#cfe3d6]" aria-hidden="true">RL</span>
    <div><b class="block text-base">Rosa Lindqvist</b><span class="text-[.82rem] text-nc-muted">Urban ecologist, London</span></div>
  </div>
  <h3 class="font-nc-serif text-[1.35rem] leading-[1.25] font-bold tracking-[-.01em]" id="us-title">Understory</h3>
  <p class="mt-1.5 mb-3.5 text-nc-muted">The trees on your street, who planted them and what they are up to. Short, illustrated, every Sunday.</p>
  <ul class="mb-[18px] flex flex-wrap gap-1.5" aria-label="About this newsletter">
    <li class="rounded-full border border-[#d3e3d8] bg-nc-soft px-2.5 py-[3px] text-[.76rem] font-semibold text-[#1e4d38]">212 issues</li><li class="rounded-full border border-[#d3e3d8] bg-nc-soft px-2.5 py-[3px] text-[.76rem] font-semibold text-[#1e4d38]">9,870 subscribers</li><li class="rounded-full border border-[#d3e3d8] bg-nc-soft px-2.5 py-[3px] text-[.76rem] font-semibold text-[#1e4d38]">Free</li>
  </ul>
  <form action="#" method="post" data-nf="us-done">
    <label class="mb-1.5 block text-[.84rem] font-[650]" for="us-email">Your email</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-nc-line bg-white px-[13px] py-2.5 text-nc-ink transition-[border-color,box-shadow] duration-150 focus:border-nc-accent focus:shadow-[0_0_0_4px_rgba(31,111,80,.24)] focus:outline-none [&.is-invalid]:border-nc-error motion-reduce:transition-none max-[420px]:basis-auto" id="us-email" name="email" type="email" autocomplete="email" required aria-describedby="us-email-err us-hint" data-err="us-email-err"
        data-missing="Enter your email address to subscribe." data-bad="Enter an email address with an @ and a domain, like [email protected].">
      <button class="flex-none cursor-pointer rounded-[10px] bg-nc-accent px-[18px] py-2.5 font-[650] text-white transition-colors duration-150 hover:bg-nc-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-nc-accent motion-reduce:transition-none" type="submit">Subscribe</button>
    </div>
    <p class="mt-[7px] flex items-start gap-[7px] text-[.82rem] font-semibold text-nc-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-nc-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="us-email-err"></p>
    <p class="mt-2 text-[.8rem] text-nc-muted" id="us-hint">Your address stays with Rosa. No ads, no sharing.</p>
  </form>
  <div class="flex items-start gap-3 rounded-xl bg-nc-soft px-4 py-3.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-nc-accent [&[hidden]]:hidden" id="us-done" tabindex="-1" hidden>
    <span class="grid size-8 flex-none place-items-center rounded-full bg-nc-accent text-white" aria-hidden="true"><svg class="size-4"><use href="#nc-i-tick"/></svg></span>
    <div><b class="block text-[.95rem]">One more step</b><p class="text-[.86rem] text-nc-muted">Check <span class="font-semibold wrap-anywhere text-nc-ink" data-email>your inbox</span> for a confirmation link from Rosa.</p></div>
  </div>
  <a class="mt-4 block border-t border-dashed border-[#cdd6cf] pt-3.5 text-[.84rem] font-semibold text-nc-accent-dark underline decoration-1 underline-offset-3 hover:decoration-2 focus-visible:rounded-[3px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nc-accent" href="#last-issue">Last week: The plane trees of Pimlico, and why they shed their bark</a>
</section>
</div>
</div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#56604f] uppercase">Footer with a newsletter column</span>
<footer class="rounded-[22px] border border-[#e1ddd0] bg-[#f8f6ef] px-[34px] pt-[34px] pb-[22px] text-[.92rem] leading-normal max-[420px]:px-5 max-[420px]:pt-[26px] max-[420px]:pb-[18px]">
  <div class="grid grid-cols-[2fr_1fr_1fr_1fr] gap-7 max-[760px]:grid-cols-3 max-[420px]:grid-cols-2">
    <div class="min-w-0 max-[760px]:col-span-full">
      <p class="font-nc-serif text-[1.25rem] font-bold">Brasswick Kitchen Supply</p>
      <p class="mt-1.5 text-[.86rem] text-nc-muted">Cast iron, carbon steel and the know-how to look after it.</p>
      <form class="mt-[18px]" action="#" method="post" data-nf="bk-done">
        <label class="mb-1.5 block text-[.84rem] font-[650]" for="bk-email">Get recipes and restock alerts</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-nc-line bg-white px-[13px] py-2.5 text-nc-ink transition-[border-color,box-shadow] duration-150 focus:border-nc-accent focus:shadow-[0_0_0_4px_rgba(31,111,80,.24)] focus:outline-none [&.is-invalid]:border-nc-error motion-reduce:transition-none max-[420px]:basis-auto" id="bk-email" name="email" type="email" autocomplete="email" required aria-describedby="bk-email-err bk-hint" data-err="bk-email-err"
            data-missing="Enter your email address to sign up." data-bad="Enter an email address with an @ and a domain, like [email protected].">
          <button class="flex-none cursor-pointer rounded-[10px] bg-nc-accent px-[18px] py-2.5 font-[650] text-white transition-colors duration-150 hover:bg-nc-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-nc-accent motion-reduce:transition-none" type="submit">Sign up</button>
        </div>
        <p class="mt-[7px] flex items-start gap-[7px] text-[.82rem] font-semibold text-nc-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-nc-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="bk-email-err"></p>
        <p class="mt-2 text-[.8rem] text-nc-muted" id="bk-hint">Two emails a month. Read our <a class="font-semibold text-nc-accent-dark underline decoration-1 underline-offset-3 hover:decoration-2 focus-visible:rounded-[3px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nc-accent" href="#privacy">privacy policy</a>.</p>
        <p class="mt-2.5 flex flex-wrap gap-x-3.5 gap-y-1 text-[.78rem] text-nc-muted"><span class="inline-flex items-center gap-[5px]"><svg class="size-3.5" aria-hidden="true"><use href="#nc-i-users"/></svg>31,200 cooks</span><span class="inline-flex items-center gap-[5px]"><svg class="size-3.5" aria-hidden="true"><use href="#nc-i-cal"/></svg>Issue 87 out 1 October</span></p>
      </form>
      <div class="flex items-start gap-3 rounded-xl bg-nc-soft px-4 py-3.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-nc-accent [&[hidden]]:hidden" id="bk-done" tabindex="-1" hidden>
        <span class="grid size-8 flex-none place-items-center rounded-full bg-nc-accent text-white" aria-hidden="true"><svg class="size-4"><use href="#nc-i-tick"/></svg></span>
        <div><b class="block text-[.95rem]">Almost done</b><p class="text-[.86rem] text-nc-muted">Confirm from the email we sent to <span class="font-semibold wrap-anywhere text-nc-ink" data-email>you</span> and the October issue is yours.</p></div>
      </div>
    </div>
    <nav aria-label="Shop"><h3 class="mb-2.5 text-[.74rem] font-bold tracking-[.1em] text-nc-muted uppercase">Shop</h3><ul class="grid gap-[7px] [&_a]:font-medium [&_a]:hover:underline [&_a]:focus-visible:rounded-[3px] [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-offset-2 [&_a]:focus-visible:outline-nc-accent"><li><a href="#skillets">Skillets</a></li><li><a href="#dutch-ovens">Dutch ovens</a></li><li><a href="#woks">Carbon steel woks</a></li><li><a href="#care">Care kits</a></li></ul></nav>
    <nav aria-label="Learn"><h3 class="mb-2.5 text-[.74rem] font-bold tracking-[.1em] text-nc-muted uppercase">Learn</h3><ul class="grid gap-[7px] [&_a]:font-medium [&_a]:hover:underline [&_a]:focus-visible:rounded-[3px] [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-offset-2 [&_a]:focus-visible:outline-nc-accent"><li><a href="#seasoning">Seasoning guide</a></li><li><a href="#recipes">Recipes</a></li><li><a href="#journal">Journal</a></li></ul></nav>
    <nav aria-label="Help"><h3 class="mb-2.5 text-[.74rem] font-bold tracking-[.1em] text-nc-muted uppercase">Help</h3><ul class="grid gap-[7px] [&_a]:font-medium [&_a]:hover:underline [&_a]:focus-visible:rounded-[3px] [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-offset-2 [&_a]:focus-visible:outline-nc-accent"><li><a href="#delivery">Delivery</a></li><li><a href="#returns">Returns</a></li><li><a href="#warranty">Lifetime warranty</a></li><li><a href="#contact">Contact</a></li></ul></nav>
  </div>
  <div class="mt-[30px] flex flex-wrap justify-between gap-2.5 border-t border-[#e1ddd0] pt-4 text-[.8rem] text-nc-muted"><span>© 2026 Brasswick Kitchen Supply Ltd, Sheffield</span><span class="[&_a]:font-medium [&_a]:underline [&_a]:underline-offset-3 [&_a]:focus-visible:rounded-[3px] [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-offset-2 [&_a]:focus-visible:outline-nc-accent"><a href="#terms">Terms</a> · <a href="#privacy">Privacy</a></span></div>
</footer>
</div>
CSS
@theme {
  --color-nc-ink: #18231d;
  --color-nc-muted: #4f5b53;
  --color-nc-line: #7d8a80;
  --color-nc-accent: #1f6f50;
  --color-nc-accent-dark: #165239;
  --color-nc-error: #b42318;
  --color-nc-soft: #eef4ef;
  --font-nc-serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
}
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 = '';
    if (el.tagName === 'FIELDSET') { if (!el.querySelector('input:checked')) msg = el.getAttribute('data-missing'); }
    else if (!el.checkValidity()) msg = 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 (el.tagName !== 'FIELDSET') { 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.tagName === 'FIELDSET' ? first.querySelector('input') : first).focus(); return; }
      if (!done) return;
      done.querySelectorAll('[data-email]').forEach(function (n) { n.textContent = email.value; });
      form.hidden = true; done.hidden = false; done.focus();
    });
    // once a field shows an error, re-check it while it is being fixed
    ['input', 'change'].forEach(function (type) {
      form.addEventListener(type, function (e) { var el = e.target.closest('.is-invalid'); if (el) check(el); });
    });
    var back = done && done.querySelector('[data-nf-back]');
    if (back) back.addEventListener('click', function () { done.hidden = true; form.hidden = false; email.focus(); });
  });
})();

About this set

Two newsletter cards and a site footer with a newsletter column, each built to sell the newsletter before asking for an address. Issue card shows a farming newsletter’s latest issue on a cover drawn with CSS gradients, then a stats strip with the issue count, reader count and the day it arrives, a one-field form and a Read a sample issue link. Author card is a personal newsletter: an initials avatar, the writer’s name and city, chips for the issue count, subscriber count and price, the form, and a link to last week’s issue. Footer with a newsletter column is a shop footer with the brand, a sign-up for recipes and restock alerts, a reader count and the next issue date, beside three navigation columns and a bottom bar. Use the cards in sidebars, on author pages or at the end of posts, and the footer anywhere a site-wide sign-up belongs. The stats are a description list, the footer columns are labelled nav elements, and the layouts collapse to one or two columns on phones. A short vanilla script validates the address, writes errors into the linked message and swaps in a confirmation; nothing is sent.

What’s included

  • Issue count, reader count and send day as a description list
  • Sample issue and last issue links next to the form
  • Cover art drawn with CSS gradients, no image files
  • Footer newsletter column beside three labelled nav columns
  • Bootstrap version uses .card, .badge, the grid .row and .form-control

Accessibility

Each form has a visible label, autocomplete="email", and a hint and error message linked with aria-describedby; invalid input gets aria-invalid and focus, and the confirmation is focused when it appears. Footer link groups are nav elements with their own aria-label, decorative icons and the avatar are aria-hidden, and every link and control has a visible focus style. Transitions switch off under prefers-reduced-motion.

Customise it

Set --nc-accent, --nc-accent-dark and --nc-ring on .nc for the brand colour, --nc-line for borders and --nc-soft for the confirmation background. The cover art lives in the .nc-cover background layers. In Tailwind change the nc-* colours in @theme.