Open demo

CSS code

HTML
<div><span class="lbl">Centred waitlist with an inline field</span>
<section class="wl wl-center" aria-labelledby="wl-orbit-title">
  <nav class="wl-nav" aria-label="Orbit Notes">
    <a class="wl-logo" href="#"><svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true" focusable="false"><circle cx="14" cy="14" r="13" fill="#6d28d9"/><ellipse cx="14" cy="14" rx="11" ry="4.5" fill="none" stroke="#fff" stroke-width="2" transform="rotate(-25 14 14)"/><circle cx="14" cy="14" r="3.5" fill="#fff"/></svg>Orbit Notes</a>
    <a href="#">Sign in</a>
  </nav>
  <p class="wl-badge"><i aria-hidden="true"></i>Private beta, opening in waves</p>
  <h1 class="wl-title" id="wl-orbit-title">Your notes, even with no signal</h1>
  <p class="wl-sub">Orbit Notes saves everything on your device first and syncs when you are back online, so a tunnel never eats a meeting note again.</p>
  <form action="#">
    <div class="wl-field">
      <label class="wl-label" for="wl-orbit-email">Email address</label>
      <div class="wl-row">
        <input class="wl-input" id="wl-orbit-email" name="email" type="email" autocomplete="email" required placeholder="[email protected]" aria-describedby="wl-orbit-hint wl-orbit-err">
        <button class="wl-btn" type="submit">Join the waitlist</button>
      </div>
      <p class="wl-hint" id="wl-orbit-hint">One email when your invite is ready. Nothing else.</p>
      <p class="wl-err" id="wl-orbit-err"><svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm-.9 4.5h1.8v5.5H9.1V6Zm.9 9a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>Enter an email address like [email protected]</p>
    </div>
  </form>
  <div class="wl-crowd"><ul aria-hidden="true"><li>AK</li><li>MR</li><li>JT</li><li>SN</li></ul><p><strong>12,480 people</strong> are already on the list</p></div>
</section></div>

<div><span class="lbl">Split newsletter sign-up with options</span>
<section class="wl wl-split" aria-labelledby="wl-brew-title">
  <div class="wl-photo"><img src="https://images.unsplash.com/photo-1509042239860-f550ce710b93?auto=format&fit=crop&w=1000&q=80" alt="Two cups of flat white with latte art beside a potted plant" width="1000" height="1200"></div>
  <div class="wl-pane">
    <p class="wl-kicker">Slow Brew Weekly</p>
    <h2 class="wl-title" id="wl-brew-title">Better coffee at home, one Sunday email at a time</h2>
    <p class="wl-sub">A recipe, a roaster worth knowing and one thing to stop doing with your grinder. Read by 38,000 home baristas.</p>
    <form action="#">
      <div class="wl-field">
        <label class="wl-label" for="wl-brew-name">First name <span class="wl-opt">(optional)</span></label>
        <input class="wl-input" id="wl-brew-name" name="name" autocomplete="given-name">
      </div>
      <div class="wl-field">
        <label class="wl-label" for="wl-brew-email">Email address</label>
        <input class="wl-input" id="wl-brew-email" name="email" type="email" autocomplete="email" required aria-describedby="wl-brew-hint wl-brew-err">
        <p class="wl-hint" id="wl-brew-hint">We never share it. Unsubscribe in one click.</p>
        <p class="wl-err" id="wl-brew-err"><svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm-.9 4.5h1.8v5.5H9.1V6Zm.9 9a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>Enter an email address like [email protected]</p>
      </div>
      <fieldset class="wl-choices">
        <legend>How often?</legend>
        <label class="wl-choice"><input type="radio" name="often" value="weekly" checked>Every Sunday</label>
        <label class="wl-choice"><input type="radio" name="often" value="monthly">Monthly round-up</label>
      </fieldset>
      <button class="wl-btn" type="submit">Subscribe free</button>
    </form>
  </div>
</section></div>
CSS
/* Email capture heroes: a waitlist form and a newsletter sign-up, each with a visible label, a hint tied to the field
   with aria-describedby, and an error message that appears through :user-invalid only after the visitor has interacted.
   The browser's own required and type="email" validation stops an empty or malformed submit. No JavaScript. */
.wl{--wl-ink:#1e1b4b;--wl-muted:#4c4f6b;--wl-accent:#6d28d9;--wl-accent-hover:#5b21b6;--wl-field:#8b8aa8;--wl-error:#b91c1c;--wl-radius:28px;
  border-radius:var(--wl-radius);color:var(--wl-ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;line-height:1.5}
.wl :where(h1,h2,h3,p,ul,fieldset,legend){margin:0;padding:0}
.wl fieldset{border:0;min-width:0}
.wl a{color:inherit;text-decoration:none}
.wl :is(a,button,input):focus-visible{outline:3px solid var(--wl-accent);outline-offset:3px}
.wl-btn{display:inline-flex;align-items:center;justify-content:center;min-height:50px;padding:0 24px;border:0;border-radius:999px;background:var(--wl-accent);
  color:#fff;font:inherit;font-weight:700;white-space:nowrap;cursor:pointer;transition:background-color .2s}
.wl-btn:hover{background:var(--wl-accent-hover)}
.wl-input{width:100%;min-height:50px;padding:0 18px;border:1px solid var(--wl-field);border-radius:999px;background:#fff;color:var(--wl-ink);font:inherit}
.wl-input::placeholder{color:#64607d}
.wl-input:user-invalid{border-color:var(--wl-error);box-shadow:0 0 0 1px var(--wl-error)}
.wl-label{display:block;margin-bottom:8px;font-size:.9rem;font-weight:700}
.wl-hint{margin-top:10px;font-size:.86rem;color:var(--wl-muted)}
.wl-err{display:none;align-items:center;gap:8px;margin-top:10px;font-size:.86rem;font-weight:600;color:var(--wl-error)}
.wl-field:has(.wl-input:user-invalid) .wl-err{display:flex}
.wl-field:has(.wl-input:user-invalid) .wl-hint{display:none}
/* centred waitlist */
.wl-center{position:relative;overflow:hidden;padding:24px 24px 72px;text-align:center;
  background:radial-gradient(40% 50% at 20% 0%,#ede9fe,rgba(237,233,254,0) 70%),radial-gradient(40% 50% at 80% 10%,#fce7f3,rgba(252,231,243,0) 70%),#faf8ff}
.wl-nav{display:flex;align-items:center;justify-content:space-between;max-width:1060px;margin:0 auto}
.wl-logo{display:inline-flex;align-items:center;gap:10px;font-weight:800;letter-spacing:-.02em;border-radius:8px}
.wl-nav a:last-child{font-size:.92rem;font-weight:600;border-radius:8px}
.wl-badge{display:inline-flex;align-items:center;gap:8px;margin-top:64px;padding:6px 14px;border-radius:999px;background:#fff;border:1px solid #ddd6fe;font-size:.84rem;font-weight:600;color:var(--wl-accent-hover)}
.wl-badge i{width:8px;height:8px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px #dcfce7}
.wl-title{max-width:760px;margin:22px auto 0;font-size:clamp(2.3rem,5.2vw,3.9rem);line-height:1.05;letter-spacing:-.04em;font-weight:800}
.wl-sub{max-width:560px;margin:20px auto 0;font-size:1.1rem;color:var(--wl-muted)}
.wl-center form{max-width:520px;margin:36px auto 0;text-align:left}
.wl-row{display:flex;gap:8px;padding:6px;border:1px solid var(--wl-field);border-radius:999px;background:#fff;box-shadow:0 18px 40px -24px rgba(76,29,149,.45)}
.wl-row .wl-input{border:0;min-height:46px;padding-left:16px}
.wl-row:has(.wl-input:user-invalid){border-color:var(--wl-error);box-shadow:0 0 0 1px var(--wl-error)}
.wl-row .wl-input:user-invalid{box-shadow:none}
.wl-row .wl-btn{min-height:46px}
.wl-hint,.wl-err{padding-left:18px}
.wl-crowd{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:34px;font-size:.9rem;color:var(--wl-muted)}
.wl-crowd ul{display:flex;list-style:none}
.wl-crowd li{display:grid;place-items:center;width:36px;height:36px;margin-left:-6px;border:2px solid #faf8ff;border-radius:50%;font-size:.7rem;font-weight:800;color:#fff}
.wl-crowd li:first-child{margin-left:0;background:#6d28d9}.wl-crowd li:nth-child(2){background:#be185d}.wl-crowd li:nth-child(3){background:#0f766e}.wl-crowd li:nth-child(4){background:#b45309}
.wl-crowd strong{color:var(--wl-ink)}
/* split newsletter */
.wl-split{--wl-ink:#292018;--wl-muted:#5b4d41;--wl-accent:#92400e;--wl-accent-hover:#78350f;--wl-field:#9c8b7c;
  display:grid;grid-template-columns:1fr 1fr;overflow:hidden;background:#fbf6ef}
.wl-photo{position:relative;min-height:320px}
.wl-photo img{position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:cover}
.wl-pane{padding:56px 52px}
.wl-kicker{font-size:.8rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--wl-accent)}
.wl-split .wl-title{margin:14px 0 0;font-size:clamp(2rem,4vw,3rem);font-family:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;letter-spacing:-.02em;font-weight:700}
.wl-split .wl-sub{margin:16px 0 0;font-size:1.04rem}
.wl-split form{display:grid;gap:20px;margin-top:30px}
.wl-split .wl-input{border-radius:12px}
.wl-split .wl-hint,.wl-split .wl-err{padding-left:0;margin-top:8px}
.wl-opt{font-weight:500;color:var(--wl-muted)}
.wl-choices{display:flex;flex-wrap:wrap;gap:10px}
.wl-choices legend{margin-bottom:8px;font-size:.9rem;font-weight:700}
.wl-choice{position:relative;display:flex;align-items:center;gap:10px;padding:10px 16px;border:1px solid var(--wl-field);border-radius:12px;background:#fff;font-size:.92rem;cursor:pointer}
.wl-choice input{width:18px;height:18px;margin:0;accent-color:var(--wl-accent)}
.wl-choice:has(input:checked){border-color:var(--wl-accent);box-shadow:0 0 0 1px var(--wl-accent);background:#fff7ed}
.wl-split .wl-btn{justify-self:start;border-radius:12px}
.wl-fine{font-size:.82rem;color:var(--wl-muted)}
@media (max-width:767px){
  .wl-split{grid-template-columns:1fr}
  .wl-photo{min-height:220px}
  .wl-pane{padding:32px 22px 36px}
}
@media (max-width:560px){
  .wl-center{padding:20px 18px 52px}
  .wl-badge{margin-top:44px}
  .wl-row{flex-direction:column;border-radius:20px}
  .wl-row .wl-input{border-radius:14px}
  .wl-split .wl-btn{justify-self:stretch}
}
@media (prefers-reduced-motion:reduce){.wl *{transition:none!important}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Centred waitlist with an inline field</span>
<section class="wl wl-center text-center" aria-labelledby="wl-orbit-title">
  <nav class="wl-nav d-flex align-items-center justify-content-between mx-auto" aria-label="Orbit Notes">
    <a class="wl-logo d-inline-flex align-items-center gap-2" href="#"><svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true" focusable="false"><circle cx="14" cy="14" r="13" fill="#6d28d9"/><ellipse cx="14" cy="14" rx="11" ry="4.5" fill="none" stroke="#fff" stroke-width="2" transform="rotate(-25 14 14)"/><circle cx="14" cy="14" r="3.5" fill="#fff"/></svg>Orbit Notes</a>
    <a href="#">Sign in</a>
  </nav>
  <p class="wl-badge d-inline-flex align-items-center rounded-pill mb-0"><i aria-hidden="true"></i>Private beta, opening in waves</p>
  <h1 class="wl-title" id="wl-orbit-title">Your notes, even with no signal</h1>
  <p class="wl-sub">Orbit Notes saves everything on your device first and syncs when you are back online, so a tunnel never eats a meeting note again.</p>
  <form class="text-start" action="#">
    <div class="wl-field">
      <label class="form-label" for="wlb-orbit-email">Email address</label>
      <div class="wl-row d-flex">
        <input class="form-control" id="wlb-orbit-email" name="email" type="email" autocomplete="email" required placeholder="[email protected]" aria-describedby="wlb-orbit-hint wlb-orbit-err">
        <button class="btn wl-btn" type="submit">Join the waitlist</button>
      </div>
      <p class="form-text mb-0" id="wlb-orbit-hint">One email when your invite is ready. Nothing else.</p>
      <p class="invalid-feedback mb-0" id="wlb-orbit-err"><svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm-.9 4.5h1.8v5.5H9.1V6Zm.9 9a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>Enter an email address like [email protected]</p>
    </div>
  </form>
  <div class="wl-crowd d-flex align-items-center justify-content-center"><ul class="list-unstyled d-flex m-0" aria-hidden="true"><li>AK</li><li>MR</li><li>JT</li><li>SN</li></ul><p class="m-0"><strong>12,480 people</strong> are already on the list</p></div>
</section></div>

<div><span class="lbl">Split newsletter sign-up with options</span>
<section class="wl wl-split row g-0" aria-labelledby="wl-brew-title">
  <div class="wl-photo col-md-6 position-relative"><img src="https://images.unsplash.com/photo-1509042239860-f550ce710b93?auto=format&fit=crop&w=1000&q=80" alt="Two cups of flat white with latte art beside a potted plant" width="1000" height="1200"></div>
  <div class="wl-pane col-md-6">
    <p class="wl-kicker">Slow Brew Weekly</p>
    <h2 class="wl-title" id="wl-brew-title">Better coffee at home, one Sunday email at a time</h2>
    <p class="wl-sub">A recipe, a roaster worth knowing and one thing to stop doing with your grinder. Read by 38,000 home baristas.</p>
    <form class="d-grid" action="#">
      <div class="wl-field">
        <label class="form-label" for="wlb-brew-name">First name <span class="wl-opt">(optional)</span></label>
        <input class="form-control" id="wlb-brew-name" name="name" autocomplete="given-name">
      </div>
      <div class="wl-field">
        <label class="form-label" for="wlb-brew-email">Email address</label>
        <input class="form-control" id="wlb-brew-email" name="email" type="email" autocomplete="email" required aria-describedby="wlb-brew-hint wlb-brew-err">
        <p class="form-text mb-0" id="wlb-brew-hint">We never share it. Unsubscribe in one click.</p>
        <p class="invalid-feedback mb-0" id="wlb-brew-err"><svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm-.9 4.5h1.8v5.5H9.1V6Zm.9 9a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>Enter an email address like [email protected]</p>
      </div>
      <fieldset class="wl-choices d-flex flex-wrap gap-2">
        <legend>How often?</legend>
        <label class="wl-choice d-flex align-items-center"><input class="form-check-input" type="radio" name="often" value="weekly" checked>Every Sunday</label>
        <label class="wl-choice d-flex align-items-center"><input class="form-check-input" type="radio" name="often" value="monthly">Monthly round-up</label>
      </fieldset>
      <button class="btn wl-btn" type="submit">Subscribe free</button>
    </form>
  </div>
</section></div>
CSS
/* Email capture heroes in Bootstrap 5: .form-label, .form-control, .form-text, .invalid-feedback, .form-check-input and .btn.
   Bootstrap normally shows .invalid-feedback only after JavaScript adds .was-validated; here :user-invalid shows it
   once the visitor has interacted, and required plus type="email" stop a bad submit. No JavaScript. */
.wl{--wl-ink:#1e1b4b;--wl-muted:#4c4f6b;--wl-accent:#6d28d9;--wl-accent-hover:#5b21b6;--wl-field:#8b8aa8;--wl-error:#b91c1c;--wl-radius:28px;
  border-radius:var(--wl-radius);color:var(--wl-ink)}
.wl a{color:inherit;text-decoration:none}
.wl :is(a,.btn,.form-control,.form-check-input):focus-visible{outline:3px solid var(--wl-accent);outline-offset:3px;box-shadow:none}
.wl-btn{--bs-btn-padding-x:24px;--bs-btn-font-weight:700;--bs-btn-border-radius:999px;--bs-btn-border-width:0;--bs-btn-color:#fff;--bs-btn-bg:var(--wl-accent);
  --bs-btn-hover-color:#fff;--bs-btn-hover-bg:var(--wl-accent-hover);--bs-btn-active-color:#fff;--bs-btn-active-bg:var(--wl-accent-hover);
  display:inline-flex;align-items:center;justify-content:center;min-height:50px;white-space:nowrap;transition:background-color .2s}
.wl .form-control{--bs-border-color:var(--wl-field);min-height:50px;padding:0 18px;border-radius:999px;color:var(--wl-ink)}
.wl .form-control::placeholder{color:#64607d}
.wl .form-control:focus{border-color:var(--wl-field);box-shadow:none}
.wl .form-control:user-invalid{border-color:var(--wl-error);box-shadow:0 0 0 1px var(--wl-error)}
.wl .form-label{margin-bottom:8px;font-size:.9rem;font-weight:700}
.wl .form-text{margin-top:10px;font-size:.86rem;color:var(--wl-muted)}
.wl .invalid-feedback{align-items:center;gap:8px;margin-top:10px;font-size:.86rem;font-weight:600;color:var(--wl-error)}
.wl-field:has(.form-control:user-invalid) .invalid-feedback{display:flex}
.wl-field:has(.form-control:user-invalid) .form-text{display:none}
.wl-center{position:relative;overflow:hidden;padding:24px 24px 72px;
  background:radial-gradient(40% 50% at 20% 0%,#ede9fe,rgba(237,233,254,0) 70%),radial-gradient(40% 50% at 80% 10%,#fce7f3,rgba(252,231,243,0) 70%),#faf8ff}
.wl-nav{max-width:1060px}
.wl-logo{font-weight:800;letter-spacing:-.02em;border-radius:8px}
.wl-nav a:last-child{font-size:.92rem;font-weight:600;border-radius:8px}
.wl-badge{gap:8px;margin-top:64px;padding:6px 14px;border:1px solid #ddd6fe;background:#fff;font-size:.84rem;font-weight:600;color:var(--wl-accent-hover)}
.wl-badge i{width:8px;height:8px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px #dcfce7}
.wl-title{max-width:760px;margin:22px auto 0;font-size:clamp(2.3rem,5.2vw,3.9rem);line-height:1.05;letter-spacing:-.04em;font-weight:800}
.wl-sub{max-width:560px;margin:20px auto 0;font-size:1.1rem;color:var(--wl-muted)}
.wl-center form{max-width:520px;margin:36px auto 0}
.wl-row{gap:8px;padding:6px;border:1px solid var(--wl-field);border-radius:999px;background:#fff;box-shadow:0 18px 40px -24px rgba(76,29,149,.45)}
.wl-row .form-control{border:0;min-height:46px;padding-left:16px}
.wl-row .form-control:user-invalid{box-shadow:none}
.wl-row:has(.form-control:user-invalid){border-color:var(--wl-error);box-shadow:0 0 0 1px var(--wl-error)}
.wl-row .wl-btn{min-height:46px}
.wl-center :is(.form-text,.invalid-feedback){padding-left:18px}
.wl-crowd{gap:12px;margin-top:34px;font-size:.9rem;color:var(--wl-muted)}
.wl-crowd li{display:grid;place-items:center;width:36px;height:36px;margin-left:-6px;border:2px solid #faf8ff;border-radius:50%;font-size:.7rem;font-weight:800;color:#fff}
.wl-crowd li:first-child{margin-left:0;background:#6d28d9}.wl-crowd li:nth-child(2){background:#be185d}.wl-crowd li:nth-child(3){background:#0f766e}.wl-crowd li:nth-child(4){background:#b45309}
.wl-crowd strong{color:var(--wl-ink)}
.wl-split{--wl-ink:#292018;--wl-muted:#5b4d41;--wl-accent:#92400e;--wl-accent-hover:#78350f;--wl-field:#9c8b7c;overflow:hidden;background:#fbf6ef}
.wl-photo{min-height:320px}
.wl-photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.wl-pane{padding:56px 52px}
.wl-kicker{margin:0;font-size:.8rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--wl-accent)}
.wl-split .wl-title{margin:14px 0 0;font-size:clamp(2rem,4vw,3rem);font-family:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;letter-spacing:-.02em;font-weight:700}
.wl-split .wl-sub{margin:16px 0 0;font-size:1.04rem}
.wl-split form{gap:20px;margin-top:30px}
.wl-split .form-control{border-radius:12px}
.wl-split :is(.form-text,.invalid-feedback){margin-top:8px}
.wl-opt{font-weight:500;color:var(--wl-muted)}
.wl-choices{min-width:0}
.wl-choices legend{float:none;margin-bottom:8px;font-size:.9rem;font-weight:700}
.wl-choice{gap:10px;padding:10px 16px;border:1px solid var(--wl-field);border-radius:12px;background:#fff;font-size:.92rem;cursor:pointer}
.wl-choice .form-check-input{--bs-border-color:var(--wl-field);width:18px;height:18px;margin:0;float:none}
.wl-choice .form-check-input:checked{background-color:var(--wl-accent);border-color:var(--wl-accent)}
.wl-choice:has(:checked){border-color:var(--wl-accent);box-shadow:0 0 0 1px var(--wl-accent);background:#fff7ed}
.wl-split .wl-btn{--bs-btn-border-radius:12px;justify-self:start}
@media (max-width:767.98px){.wl-photo{min-height:220px}.wl-pane{padding:32px 22px 36px}}
@media (max-width:560px){
  .wl-center{padding:20px 18px 52px}
  .wl-badge{margin-top:44px}
  .wl-row{flex-direction:column;border-radius:20px}
  .wl-row .form-control{border-radius:14px}
  .wl-split .wl-btn{justify-self:stretch}
}
@media (prefers-reduced-motion:reduce){.wl *{transition:none!important}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5567] uppercase">Centred waitlist with an inline field</span>
<section class="relative overflow-hidden rounded-[28px] [background:radial-gradient(40%_50%_at_20%_0%,#ede9fe,rgba(237,233,254,0)_70%),radial-gradient(40%_50%_at_80%_10%,#fce7f3,rgba(252,231,243,0)_70%),#faf8ff] px-6 pt-6 pb-[72px] text-center leading-normal text-[#1e1b4b] max-[560px]:px-[18px] max-[560px]:pt-5 max-[560px]:pb-[52px]" aria-labelledby="wl-orbit-title">
  <nav class="mx-auto flex max-w-[1060px] items-center justify-between" aria-label="Orbit Notes">
    <a class="inline-flex items-center gap-2.5 rounded-lg font-extrabold tracking-[-.02em] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#6d28d9]" href="#"><svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true" focusable="false"><circle cx="14" cy="14" r="13" fill="#6d28d9"/><ellipse cx="14" cy="14" rx="11" ry="4.5" fill="none" stroke="#fff" stroke-width="2" transform="rotate(-25 14 14)"/><circle cx="14" cy="14" r="3.5" fill="#fff"/></svg>Orbit Notes</a>
    <a class="rounded-lg text-[.92rem] font-semibold focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#6d28d9]" href="#">Sign in</a>
  </nav>
  <p class="mt-16 inline-flex items-center gap-2 rounded-full border border-[#ddd6fe] bg-white px-3.5 py-1.5 text-[.84rem] font-semibold text-[#5b21b6] max-[560px]:mt-11"><i class="size-2 rounded-full bg-[#16a34a] shadow-[0_0_0_4px_#dcfce7]" aria-hidden="true"></i>Private beta, opening in waves</p>
  <h1 class="mx-auto mt-[22px] max-w-[760px] text-[clamp(2.3rem,5.2vw,3.9rem)] leading-[1.05] font-extrabold tracking-[-.04em]" id="wl-orbit-title">Your notes, even with no signal</h1>
  <p class="mx-auto mt-5 max-w-[560px] text-[1.1rem] text-[#4c4f6b]">Orbit Notes saves everything on your device first and syncs when you are back online, so a tunnel never eats a meeting note again.</p>
  <form class="mx-auto mt-9 max-w-[520px] text-left" action="#">
    <div class="group">
      <label class="mb-2 block text-[.9rem] font-bold" for="wlt-orbit-email">Email address</label>
      <div class="flex gap-2 rounded-full border border-[#8b8aa8] bg-white p-1.5 shadow-[0_18px_40px_-24px_rgba(76,29,149,.45)] group-has-[input:user-invalid]:border-[#b91c1c] group-has-[input:user-invalid]:shadow-[0_0_0_1px_#b91c1c] max-[560px]:flex-col max-[560px]:rounded-[20px]">
        <input class="min-h-[46px] w-full rounded-full bg-white pl-4 text-[#1e1b4b] placeholder:text-[#64607d] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#6d28d9] max-[560px]:rounded-[14px]" id="wlt-orbit-email" name="email" type="email" autocomplete="email" required placeholder="[email protected]" aria-describedby="wlt-orbit-hint wlt-orbit-err">
        <button class="inline-flex min-h-[46px] cursor-pointer items-center justify-center rounded-full bg-[#6d28d9] px-6 font-bold whitespace-nowrap text-white transition-[color,background-color,border-color,text-decoration-color] duration-200 hover:bg-[#5b21b6] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#6d28d9] motion-reduce:transition-none" type="submit">Join the waitlist</button>
      </div>
      <p class="mt-2.5 pl-[18px] text-[.86rem] text-[#4c4f6b] group-has-[input:user-invalid]:hidden" id="wlt-orbit-hint">One email when your invite is ready. Nothing else.</p>
      <p class="mt-2.5 hidden items-center gap-2 pl-[18px] text-[.86rem] font-semibold text-[#b91c1c] group-has-[input:user-invalid]:flex" id="wlt-orbit-err"><svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm-.9 4.5h1.8v5.5H9.1V6Zm.9 9a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>Enter an email address like [email protected]</p>
    </div>
  </form>
  <div class="mt-[34px] flex items-center justify-center gap-3 text-[.9rem] text-[#4c4f6b]"><ul class="flex" aria-hidden="true"><li class="grid size-9 place-items-center rounded-full border-2 border-[#faf8ff] bg-[#6d28d9] text-[.7rem] font-extrabold text-white">AK</li><li class="-ml-1.5 grid size-9 place-items-center rounded-full border-2 border-[#faf8ff] bg-[#be185d] text-[.7rem] font-extrabold text-white">MR</li><li class="-ml-1.5 grid size-9 place-items-center rounded-full border-2 border-[#faf8ff] bg-[#0f766e] text-[.7rem] font-extrabold text-white">JT</li><li class="-ml-1.5 grid size-9 place-items-center rounded-full border-2 border-[#faf8ff] bg-[#b45309] text-[.7rem] font-extrabold text-white">SN</li></ul><p><strong class="text-[#1e1b4b]">12,480 people</strong> are already on the list</p></div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5567] uppercase">Split newsletter sign-up with options</span>
<section class="grid grid-cols-1 overflow-hidden rounded-[28px] bg-[#fbf6ef] leading-normal text-[#292018] md:grid-cols-2" aria-labelledby="wl-brew-title">
  <div class="relative min-h-[220px] md:min-h-80"><img class="absolute inset-0 block size-full object-cover" src="https://images.unsplash.com/photo-1509042239860-f550ce710b93?auto=format&fit=crop&w=1000&q=80" alt="Two cups of flat white with latte art beside a potted plant" width="1000" height="1200"></div>
  <div class="px-[22px] pt-8 pb-9 md:px-[52px] md:py-14">
    <p class="text-[.8rem] font-bold tracking-[.12em] text-[#92400e] uppercase">Slow Brew Weekly</p>
    <h2 class="mt-3.5 font-['Iowan_Old_Style','Palatino_Linotype',Palatino,Georgia,serif] text-[clamp(2rem,4vw,3rem)] leading-[1.05] font-bold tracking-[-.02em]" id="wl-brew-title">Better coffee at home, one Sunday email at a time</h2>
    <p class="mt-4 text-[1.04rem] text-[#5b4d41]">A recipe, a roaster worth knowing and one thing to stop doing with your grinder. Read by 38,000 home baristas.</p>
    <form class="mt-[30px] grid gap-5" action="#">
      <div>
        <label class="mb-2 block text-[.9rem] font-bold" for="wlt-brew-name">First name <span class="font-medium text-[#5b4d41]">(optional)</span></label>
        <input class="min-h-[50px] w-full rounded-xl border border-[#9c8b7c] bg-white px-[18px] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#92400e]" id="wlt-brew-name" name="name" autocomplete="given-name">
      </div>
      <div class="group">
        <label class="mb-2 block text-[.9rem] font-bold" for="wlt-brew-email">Email address</label>
        <input class="min-h-[50px] w-full rounded-xl border border-[#9c8b7c] bg-white px-[18px] user-invalid:border-[#b91c1c] user-invalid:shadow-[0_0_0_1px_#b91c1c] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#92400e]" id="wlt-brew-email" name="email" type="email" autocomplete="email" required aria-describedby="wlt-brew-hint wlt-brew-err">
        <p class="mt-2 text-[.86rem] text-[#5b4d41] group-has-[input:user-invalid]:hidden" id="wlt-brew-hint">We never share it. Unsubscribe in one click.</p>
        <p class="mt-2 hidden items-center gap-2 text-[.86rem] font-semibold text-[#b91c1c] group-has-[input:user-invalid]:flex" id="wlt-brew-err"><svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm-.9 4.5h1.8v5.5H9.1V6Zm.9 9a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>Enter an email address like [email protected]</p>
      </div>
      <fieldset class="flex min-w-0 flex-wrap gap-2.5">
        <legend class="mb-2 text-[.9rem] font-bold">How often?</legend>
        <label class="flex cursor-pointer items-center gap-2.5 rounded-xl border border-[#9c8b7c] bg-white px-4 py-2.5 text-[.92rem] has-checked:border-[#92400e] has-checked:bg-[#fff7ed] has-checked:shadow-[0_0_0_1px_#92400e]"><input class="m-0 size-[18px] accent-[#92400e] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#92400e]" type="radio" name="often" value="weekly" checked>Every Sunday</label>
        <label class="flex cursor-pointer items-center gap-2.5 rounded-xl border border-[#9c8b7c] bg-white px-4 py-2.5 text-[.92rem] has-checked:border-[#92400e] has-checked:bg-[#fff7ed] has-checked:shadow-[0_0_0_1px_#92400e]"><input class="m-0 size-[18px] accent-[#92400e] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#92400e]" type="radio" name="often" value="monthly">Monthly round-up</label>
      </fieldset>
      <button class="inline-flex min-h-[50px] cursor-pointer items-center justify-center justify-self-start rounded-xl bg-[#92400e] px-6 font-bold whitespace-nowrap text-white transition-[color,background-color,border-color,text-decoration-color] duration-200 hover:bg-[#78350f] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#92400e] motion-reduce:transition-none max-[560px]:justify-self-stretch" type="submit">Subscribe free</button>
    </form>
  </div>
</section></div>

About this set

Two heroes built around an email field, for waitlists, early access and newsletters. Centred waitlist is a private beta hero with a status badge, a headline, an email field and a Join the waitlist button inside one rounded bar, a hint line underneath and a count of people already waiting. Split newsletter sign-up puts a photo beside a serif headline and a short form: an optional first name, a required email address and a choice between a weekly and a monthly email made from two radio cards. Validation needs no JavaScript. The email field is required and typed, so the browser blocks an empty or malformed submit, and :user-invalid only turns the field red after the visitor has typed and moved on, never on page load. At that point :has() swaps the hint for a written error that says what a valid address looks like. Both messages are tied to the field with aria-describedby. On phones the waitlist bar stacks the field over the button and the newsletter photo becomes a short banner.

What’s included

  • Visible labels on every field, with the optional one marked as optional
  • Hint and error text linked to the field with aria-describedby
  • Errors shown through :user-invalid only after interaction
  • Frequency choice built from native radio buttons in a fieldset
  • Bootstrap version uses .form-control, .form-text and .invalid-feedback without JavaScript

Accessibility

Each field has a visible label, the email field is type="email" and required, and the hint and error are referenced by aria-describedby. The error is written text with an icon, not only a red border. The radio choices sit in a fieldset with a legend, every control shows a 3px focus outline, and button transitions stop under prefers-reduced-motion.

Customise it

Change --wl-accent, --wl-accent-hover, --wl-field and --wl-error on .wl, or the .wl-split modifier for the warm palette. In Bootstrap the same variables restyle .form-control and .btn; in Tailwind swap the #6d28d9 and #92400e values and the user-invalid: colours.