Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="pf-i-pan" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="13" r="7"/><path d="M17 13h5M7 4c0 1.5 1 1.5 1 3M11 3c0 1.5 1 1.5 1 3"/></symbol>
  <symbol id="pf-i-bread" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 11a4 4 0 0 1 1-7.8C8 3 9.5 4 12 4s4-1 6-.8A4 4 0 0 1 19 11v8a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"/><path d="M9 9l2 2M13 9l2 2"/></symbol>
  <symbol id="pf-i-glass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M7 3h10l-.5 6a4.5 4.5 0 0 1-9 0zM12 13.5V21M8 21h8M7.3 7h9.4"/></symbol>
  <symbol id="pf-i-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12V4a1 1 0 0 1 1-1h8l9 9-9 9z"/><circle cx="8" cy="8" r="1.5"/></symbol>
  <symbol id="pf-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="pf-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
</svg>

<div><span class="lbl">Topic cards</span>
<section class="pf" aria-labelledby="pn-title">
  <form class="pf-form" action="#" method="post" data-nf="pn-done">
    <div class="pf-head"><h3 id="pn-title">Pantry & Flame newsletters</h3><p>Choose the emails you want. You can change this from any email we send.</p></div>
    <fieldset class="pf-set" aria-describedby="pn-topics-hint pn-topics-err" data-err="pn-topics-err" data-missing="Choose at least one newsletter.">
      <legend>What would you like to get?</legend>
      <p class="pf-hint" id="pn-topics-hint">Pick one or more.</p>
      <div class="pf-topics">
        <label class="pf-topic"><input class="pf-box" type="checkbox" name="topics" value="weeknight" checked>
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-pan"/></svg></span><b>Weeknight Dinners</b>
          <small>Three 30-minute recipes and the shopping list for them.</small><span class="pf-freq">Mondays</span></label>
        <label class="pf-topic"><input class="pf-box" type="checkbox" name="topics" value="baking">
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-bread"/></svg></span><b>The Proving Drawer</b>
          <small>Bread and baking, with timings that fit around work.</small><span class="pf-freq">Fridays</span></label>
        <label class="pf-topic"><input class="pf-box" type="checkbox" name="topics" value="wine">
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-glass"/></svg></span><b>Cellar Notes</b>
          <small>Six bottles under £15 and what to cook with them.</small><span class="pf-freq">Monthly</span></label>
        <label class="pf-topic"><input class="pf-box" type="checkbox" name="topics" value="deals">
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-tag"/></svg></span><b>Kit Deals</b>
          <small>Tested kitchen kit when it is genuinely discounted.</small><span class="pf-freq">Only when there's one</span></label>
      </div>
      <p class="pf-err" id="pn-topics-err"></p>
    </fieldset>
    <div class="pf-email">
      <label class="pf-label" for="pn-email">Email address</label>
      <div class="pf-row">
        <input class="pf-input" id="pn-email" name="email" type="email" autocomplete="email" required aria-describedby="pn-email-err" data-err="pn-email-err"
          data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="pf-btn" type="submit">Subscribe</button>
      </div>
      <p class="pf-err" id="pn-email-err"></p>
    </div>
  </form>
  <div class="pf-done" id="pn-done" tabindex="-1" hidden>
    <span class="pf-done-ico" aria-hidden="true"><svg><use href="#pf-i-mail"/></svg></span>
    <h3>Confirm and you're set</h3>
    <p>We sent one confirmation email to <b data-email>you</b> covering every newsletter you picked.</p>
  </div>
</section>
</div>

<div class="demo-grid">
<div><span class="lbl">Frequency radio cards</span>
<section class="pf" aria-labelledby="cb-title">
  <form class="pf-form" action="#" method="post" data-nf="cb-done">
    <div class="pf-head"><h3 id="cb-title">Circuit Brief</h3><p>Hardware and chip news without the hype.</p></div>
    <fieldset class="pf-set">
      <legend>How often?</legend>
      <div class="pf-freqs">
        <label class="pf-opt"><input class="pf-box" type="radio" name="frequency" value="daily">
          <span class="pf-opt-text"><b>Daily briefing</b><small>Weekdays at 7am</small></span><span class="pf-read">5 min read</span></label>
        <label class="pf-opt"><input class="pf-box" type="radio" name="frequency" value="weekly" checked>
          <span class="pf-opt-text"><b>Weekly roundup</b><small>Saturday mornings</small></span><span class="pf-read">12 min read</span></label>
        <label class="pf-opt"><input class="pf-box" type="radio" name="frequency" value="monthly">
          <span class="pf-opt-text"><b>Monthly highlights</b><small>First Sunday of the month</small></span><span class="pf-read">20 min read</span></label>
      </div>
    </fieldset>
    <div class="pf-email">
      <label class="pf-label" for="cb-email">Work or personal email</label>
      <div class="pf-row">
        <input class="pf-input" id="cb-email" name="email" type="email" autocomplete="email" required aria-describedby="cb-email-err" data-err="cb-email-err"
          data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="pf-btn" type="submit">Subscribe</button>
      </div>
      <p class="pf-err" id="cb-email-err"></p>
    </div>
  </form>
  <div class="pf-done" id="cb-done" tabindex="-1" hidden>
    <span class="pf-done-ico" aria-hidden="true"><svg><use href="#pf-i-mail"/></svg></span>
    <h3>Check your inbox</h3>
    <p>Confirm <b data-email>your address</b> and your first Circuit Brief arrives on schedule.</p>
  </div>
</section>
</div>

<div><span class="lbl">Interest chips with consent</span>
<section class="pf" aria-labelledby="os-title">
  <form class="pf-form" action="#" method="post" data-nf="os-done">
    <div class="pf-head"><h3 id="os-title">Off Season</h3><p>Quiet places, out of peak, at fair prices.</p></div>
    <fieldset class="pf-set" aria-describedby="os-int-err" data-err="os-int-err" data-missing="Choose at least one interest.">
      <legend>I'm interested in</legend>
      <div class="pf-chips">
        <label class="pf-chip"><input type="checkbox" name="interests" value="rail" checked><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Rail journeys</label>
        <label class="pf-chip"><input type="checkbox" name="interests" value="hiking" checked><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Hiking</label>
        <label class="pf-chip"><input type="checkbox" name="interests" value="cities"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>City breaks</label>
        <label class="pf-chip"><input type="checkbox" name="interests" value="food"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Food trips</label>
        <label class="pf-chip"><input type="checkbox" name="interests" value="islands"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Islands</label>
        <label class="pf-chip"><input type="checkbox" name="interests" value="family"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>With kids</label>
      </div>
      <p class="pf-err" id="os-int-err"></p>
    </fieldset>
    <fieldset class="pf-set">
      <legend>Send it</legend>
      <div class="pf-seg">
        <label><input type="radio" name="cadence" value="weekly" checked>Weekly</label>
        <label><input type="radio" name="cadence" value="fortnightly">Fortnightly</label>
        <label><input type="radio" name="cadence" value="monthly">Monthly</label>
      </div>
    </fieldset>
    <div class="pf-email">
      <label class="pf-label" for="os-email">Email address</label>
      <input class="pf-input" id="os-email" name="email" type="email" autocomplete="email" required aria-describedby="os-email-err" data-err="os-email-err"
        data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
      <p class="pf-err" id="os-email-err"></p>
    </div>
    <div class="pf-consent">
      <input class="pf-box" id="os-consent" name="consent" type="checkbox" required aria-describedby="os-consent-err os-consent-note" data-err="os-consent-err"
        data-missing="Tick the box to agree to receive Off Season by email.">
      <div><label for="os-consent">I agree to receive Off Season by email. I can unsubscribe at any time.</label>
        <p class="pf-hint" id="os-consent-note">See how we handle your data in our <a href="#privacy">privacy notice</a>.</p>
        <p class="pf-err" id="os-consent-err"></p></div>
    </div>
    <button class="pf-btn pf-btn--wide" type="submit">Subscribe</button>
  </form>
  <div class="pf-done" id="os-done" tabindex="-1" hidden>
    <span class="pf-done-ico" aria-hidden="true"><svg><use href="#pf-i-mail"/></svg></span>
    <h3>Nearly there</h3>
    <p>Open the email we sent to <b data-email>you</b> and confirm. Your picks are saved.</p>
  </div>
</section>
</div>
</div>
CSS
/* Preference pickers: topics as checkbox cards, frequency as radio cards, interests as chips.
   Every group is a fieldset with a legend; the cards are labels around real inputs, so clicks, Space and arrow
   keys work as normal. :has(:checked) lights a card, :has(:focus-visible) draws its focus ring. */
.pf{--pf-ink:#1b1a2e;--pf-muted:#545270;--pf-line:#7c7a96;--pf-soft-line:#dddbea;--pf-accent:#4338ca;--pf-accent-dark:#3730a3;--pf-tint:#eeedfd;
  --pf-ring:rgba(67,56,202,.24);--pf-error:#b42318;--pf-ok:#15803d;
  background:#fff;color:var(--pf-ink);border-radius:20px;padding:28px;font-size:.92rem;line-height:1.5;box-shadow:0 1px 2px rgba(27,26,46,.06),0 20px 44px -26px rgba(27,26,46,.4)}
.pf a{color:var(--pf-accent-dark);font-weight:600;text-underline-offset:2px}
.pf a:focus-visible{outline:2px solid var(--pf-accent);outline-offset:2px;border-radius:3px}
.pf-head h3{font-size:1.3rem;font-weight:750;letter-spacing:-.02em;line-height:1.25}
.pf-head p{color:var(--pf-muted);margin-top:4px}
.pf-set{border:0;min-width:0;margin-top:20px}
.pf-set legend{font-weight:700;font-size:.95rem;padding:0}
.pf-hint{font-size:.8rem;color:var(--pf-muted);margin-top:2px}
.pf-err{display:flex;gap:7px;align-items:flex-start;font-size:.82rem;font-weight:600;color:var(--pf-error);margin-top:8px}
.pf-err:empty{display:none}
.pf-err::before{content:"!";flex:none;display:grid;place-items:center;width:16px;height:16px;margin-top:2px;border-radius:50%;background:var(--pf-error);color:#fff;font-size:.7rem;font-weight:800}
/* checkbox / radio drawn with appearance:none so the same control works in all three stylesheets */
.pf-box{appearance:none;flex:none;width:20px;height:20px;margin:0;border:1.5px solid var(--pf-line);border-radius:6px;background:#fff center/13px no-repeat;cursor:pointer;
  transition:background-color .15s,border-color .15s}
.pf-box[type=radio]{border-radius:50%}
.pf-box:checked{background-color:var(--pf-accent);border-color:var(--pf-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")}
.pf-box[type=radio]:checked{background-image:radial-gradient(circle,#fff 0 3.5px,transparent 4px);background-size:auto}
.pf-box:focus-visible{outline:2px solid var(--pf-accent);outline-offset:2px}
.pf-box.is-invalid{border-color:var(--pf-error)}
/* email row shared by the three pickers */
.pf-email{margin-top:20px}
.pf-label{display:block;font-weight:650;font-size:.86rem;margin-bottom:6px}
.pf-row{display:flex;gap:8px}
.pf-input{display:block;flex:1 1 0;width:100%;min-width:0;font:inherit;color:var(--pf-ink);background:#fff;border:1.5px solid var(--pf-line);border-radius:10px;padding:10px 13px;transition:border-color .15s,box-shadow .15s}
.pf-input:focus{outline:none;border-color:var(--pf-accent);box-shadow:0 0 0 4px var(--pf-ring)}
.pf-input.is-invalid{border-color:var(--pf-error)}
.pf-btn{flex:none;font:inherit;font-weight:650;color:#fff;background:var(--pf-accent);border:0;border-radius:10px;padding:10px 18px;cursor:pointer;transition:background-color .15s}
.pf-btn:hover{background:var(--pf-accent-dark)}
.pf-btn:focus-visible{outline:2px solid var(--pf-accent);outline-offset:3px}
.pf-btn--wide{display:block;width:100%;margin-top:18px}
.pf-done{outline:none;text-align:center;padding:10px 0 4px}
.pf-done[hidden],.pf-form[hidden]{display:none}
.pf-done:focus-visible{outline:2px solid var(--pf-accent);outline-offset:4px;border-radius:12px}
.pf-done-ico{display:grid;place-items:center;width:48px;height:48px;margin:0 auto 10px;border-radius:50%;background:var(--pf-tint);color:var(--pf-accent)}
.pf-done-ico svg{width:24px;height:24px}
.pf-done h3{font-size:1.15rem;font-weight:750}
.pf-done p{color:var(--pf-muted);margin-top:4px}
.pf-done b{color:var(--pf-ink);overflow-wrap:anywhere}

/* 1. Topic cards */
.pf-topics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:12px}
.pf-topic{position:relative;display:grid;grid-template-columns:auto 1fr;gap:4px 14px;align-items:start;padding:16px 16px 14px;border:1.5px solid var(--pf-soft-line);border-radius:14px;
  cursor:pointer;transition:border-color .15s,background-color .15s,box-shadow .15s}
.pf-topic:hover{border-color:#b9b6d6}
.pf-topic:has(:checked){border-color:var(--pf-accent);background:var(--pf-tint)}
.pf-topic:has(:focus-visible){box-shadow:0 0 0 4px var(--pf-ring)}
.pf-topic .pf-box{position:absolute;top:14px;right:14px}
.pf-ico{grid-row:span 2;display:grid;place-items:center;width:42px;height:42px;border-radius:12px;background:var(--pf-tint);color:var(--pf-accent)}
.pf-topic:has(:checked) .pf-ico{background:var(--pf-accent);color:#fff}
.pf-ico svg{width:22px;height:22px}
.pf-topic b{font-size:.96rem;padding-right:28px}
.pf-topic small{display:block;font-size:.82rem;color:var(--pf-muted)}
.pf-freq{grid-column:2;justify-self:start;margin-top:6px;font-size:.72rem;font-weight:700;color:#3730a3;background:#e3e1fb;border-radius:999px;padding:2px 9px}

/* 2. Frequency radio cards */
.pf-freqs{display:grid;gap:10px;margin-top:12px}
.pf-opt{display:flex;align-items:center;gap:12px;padding:13px 14px;border:1.5px solid var(--pf-soft-line);border-radius:12px;cursor:pointer;transition:border-color .15s,background-color .15s,box-shadow .15s}
.pf-opt:hover{border-color:#b9b6d6}
.pf-opt:has(:checked){border-color:var(--pf-accent);background:var(--pf-tint)}
.pf-opt:has(:focus-visible){box-shadow:0 0 0 4px var(--pf-ring)}
.pf-opt-text{flex:1;min-width:0}
.pf-opt b{display:block;font-size:.94rem}
.pf-opt small{font-size:.8rem;color:var(--pf-muted)}
.pf-read{flex:none;font-size:.76rem;font-weight:600;color:var(--pf-muted);white-space:nowrap}

/* 3. Interest chips, a segmented frequency and a consent box */
.pf-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.pf-chip{position:relative;display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border:1.5px solid var(--pf-line);border-radius:999px;font-size:.86rem;font-weight:600;cursor:pointer;
  transition:background-color .15s,border-color .15s,color .15s}
.pf-chip input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.pf-chip svg{width:14px;height:14px;display:none}
.pf-chip:hover{border-color:var(--pf-accent)}
.pf-chip:has(:checked){background:var(--pf-accent);border-color:var(--pf-accent);color:#fff}
.pf-chip:has(:checked) svg{display:block}
.pf-chip:has(:focus-visible){outline:2px solid var(--pf-accent);outline-offset:2px}
.pf-seg{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin-top:10px;padding:4px;background:#f0eff7;border:1px solid var(--pf-soft-line);border-radius:12px}
.pf-seg label{position:relative;text-align:center;padding:8px 6px;border-radius:9px;font-size:.86rem;font-weight:600;color:var(--pf-muted);cursor:pointer;transition:background-color .15s,color .15s,box-shadow .15s}
.pf-seg input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.pf-seg label:has(:checked){background:#fff;color:var(--pf-ink);box-shadow:0 1px 3px rgba(27,26,46,.18)}
.pf-seg label:has(:focus-visible){outline:2px solid var(--pf-accent);outline-offset:1px}
.pf-consent{display:flex;gap:10px;align-items:flex-start;margin-top:16px;font-size:.84rem}
.pf-consent .pf-box{margin-top:1px}
.pf-consent label{cursor:pointer}

@media (max-width:560px){.pf-topics{grid-template-columns:1fr}}
@media (max-width:420px){.pf{padding:22px 18px}.pf-row{flex-direction:column}}
@media (prefers-reduced-motion:reduce){.pf-box,.pf-input,.pf-btn,.pf-topic,.pf-opt,.pf-chip,.pf-seg label{transition:none}}
JavaScript
// Front-end demo only: nothing is sent. On submit the script checks each field or fieldset that has data-err
// (a fieldset needs at least one ticked box), writes the message into the element it points to with
// aria-describedby, and moves focus to the first problem. A valid submit swaps in the success state.
// Without JavaScript the browser still checks the required email and consent fields.
(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); });
    });
  });
})();

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="pf-i-pan" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="13" r="7"/><path d="M17 13h5M7 4c0 1.5 1 1.5 1 3M11 3c0 1.5 1 1.5 1 3"/></symbol>
  <symbol id="pf-i-bread" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 11a4 4 0 0 1 1-7.8C8 3 9.5 4 12 4s4-1 6-.8A4 4 0 0 1 19 11v8a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"/><path d="M9 9l2 2M13 9l2 2"/></symbol>
  <symbol id="pf-i-glass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M7 3h10l-.5 6a4.5 4.5 0 0 1-9 0zM12 13.5V21M8 21h8M7.3 7h9.4"/></symbol>
  <symbol id="pf-i-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12V4a1 1 0 0 1 1-1h8l9 9-9 9z"/><circle cx="8" cy="8" r="1.5"/></symbol>
  <symbol id="pf-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="pf-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
</svg>

<div><span class="lbl">Topic cards</span>
<section class="card pf" aria-labelledby="pn-title"><div class="card-body">
  <form action="#" method="post" data-nf="pn-done">
    <div class="pf-head"><h3 id="pn-title">Pantry & Flame newsletters</h3><p>Choose the emails you want. You can change this from any email we send.</p></div>
    <fieldset class="pf-set" aria-describedby="pn-topics-hint pn-topics-err" data-err="pn-topics-err" data-missing="Choose at least one newsletter.">
      <legend>What would you like to get?</legend>
      <p class="form-text" id="pn-topics-hint">Pick one or more.</p>
      <div class="pf-topics">
        <label class="pf-topic"><input class="form-check-input" type="checkbox" name="topics" value="weeknight" checked>
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-pan"/></svg></span><b>Weeknight Dinners</b>
          <small>Three 30-minute recipes and the shopping list for them.</small><span class="badge rounded-pill">Mondays</span></label>
        <label class="pf-topic"><input class="form-check-input" type="checkbox" name="topics" value="baking">
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-bread"/></svg></span><b>The Proving Drawer</b>
          <small>Bread and baking, with timings that fit around work.</small><span class="badge rounded-pill">Fridays</span></label>
        <label class="pf-topic"><input class="form-check-input" type="checkbox" name="topics" value="wine">
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-glass"/></svg></span><b>Cellar Notes</b>
          <small>Six bottles under £15 and what to cook with them.</small><span class="badge rounded-pill">Monthly</span></label>
        <label class="pf-topic"><input class="form-check-input" type="checkbox" name="topics" value="deals">
          <span class="pf-ico" aria-hidden="true"><svg><use href="#pf-i-tag"/></svg></span><b>Kit Deals</b>
          <small>Tested kitchen kit when it is genuinely discounted.</small><span class="badge rounded-pill">Only when there's one</span></label>
      </div>
      <p class="pf-err" id="pn-topics-err"></p>
    </fieldset>
    <div class="pf-email">
      <label class="form-label" for="pn-email">Email address</label>
      <div class="d-flex gap-2 pf-row">
        <input class="form-control" id="pn-email" name="email" type="email" autocomplete="email" required aria-describedby="pn-email-err" data-err="pn-email-err"
          data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="btn pf-btn" type="submit">Subscribe</button>
      </div>
      <p class="pf-err" id="pn-email-err"></p>
    </div>
  </form>
  <div class="pf-done" id="pn-done" tabindex="-1" hidden>
    <span class="pf-done-ico" aria-hidden="true"><svg><use href="#pf-i-mail"/></svg></span>
    <h3>Confirm and you're set</h3>
    <p>We sent one confirmation email to <b data-email>you</b> covering every newsletter you picked.</p>
  </div>
</div></section>
</div>

<div class="demo-grid">
<div><span class="lbl">Frequency radio cards</span>
<section class="card pf" aria-labelledby="cb-title"><div class="card-body">
  <form action="#" method="post" data-nf="cb-done">
    <div class="pf-head"><h3 id="cb-title">Circuit Brief</h3><p>Hardware and chip news without the hype.</p></div>
    <fieldset class="pf-set">
      <legend>How often?</legend>
      <div class="pf-freqs">
        <label class="pf-opt"><input class="form-check-input" type="radio" name="frequency" value="daily">
          <span class="pf-opt-text"><b>Daily briefing</b><small>Weekdays at 7am</small></span><span class="pf-read">5 min read</span></label>
        <label class="pf-opt"><input class="form-check-input" type="radio" name="frequency" value="weekly" checked>
          <span class="pf-opt-text"><b>Weekly roundup</b><small>Saturday mornings</small></span><span class="pf-read">12 min read</span></label>
        <label class="pf-opt"><input class="form-check-input" type="radio" name="frequency" value="monthly">
          <span class="pf-opt-text"><b>Monthly highlights</b><small>First Sunday of the month</small></span><span class="pf-read">20 min read</span></label>
      </div>
    </fieldset>
    <div class="pf-email">
      <label class="form-label" for="cb-email">Work or personal email</label>
      <div class="d-flex gap-2 pf-row">
        <input class="form-control" id="cb-email" name="email" type="email" autocomplete="email" required aria-describedby="cb-email-err" data-err="cb-email-err"
          data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="btn pf-btn" type="submit">Subscribe</button>
      </div>
      <p class="pf-err" id="cb-email-err"></p>
    </div>
  </form>
  <div class="pf-done" id="cb-done" tabindex="-1" hidden>
    <span class="pf-done-ico" aria-hidden="true"><svg><use href="#pf-i-mail"/></svg></span>
    <h3>Check your inbox</h3>
    <p>Confirm <b data-email>your address</b> and your first Circuit Brief arrives on schedule.</p>
  </div>
</div></section>
</div>

<div><span class="lbl">Interest chips with consent</span>
<section class="card pf" aria-labelledby="os-title"><div class="card-body">
  <form action="#" method="post" data-nf="os-done">
    <div class="pf-head"><h3 id="os-title">Off Season</h3><p>Quiet places, out of peak, at fair prices.</p></div>
    <fieldset class="pf-set" aria-describedby="os-int-err" data-err="os-int-err" data-missing="Choose at least one interest.">
      <legend>I'm interested in</legend>
      <div class="pf-chips">
        <input class="btn-check" type="checkbox" name="interests" value="rail" id="os-i1" checked><label class="btn pf-chip" for="os-i1"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Rail journeys</label>
        <input class="btn-check" type="checkbox" name="interests" value="hiking" id="os-i2" checked><label class="btn pf-chip" for="os-i2"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Hiking</label>
        <input class="btn-check" type="checkbox" name="interests" value="cities" id="os-i3"><label class="btn pf-chip" for="os-i3"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>City breaks</label>
        <input class="btn-check" type="checkbox" name="interests" value="food" id="os-i4"><label class="btn pf-chip" for="os-i4"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Food trips</label>
        <input class="btn-check" type="checkbox" name="interests" value="islands" id="os-i5"><label class="btn pf-chip" for="os-i5"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>Islands</label>
        <input class="btn-check" type="checkbox" name="interests" value="family" id="os-i6"><label class="btn pf-chip" for="os-i6"><svg aria-hidden="true"><use href="#pf-i-tick"/></svg>With kids</label>
      </div>
      <p class="pf-err" id="os-int-err"></p>
    </fieldset>
    <fieldset class="pf-set">
      <legend>Send it</legend>
      <div class="btn-group pf-seg">
        <input class="btn-check" type="radio" name="cadence" value="weekly" id="os-c1" checked><label class="btn" for="os-c1">Weekly</label>
        <input class="btn-check" type="radio" name="cadence" value="fortnightly" id="os-c2"><label class="btn" for="os-c2">Fortnightly</label>
        <input class="btn-check" type="radio" name="cadence" value="monthly" id="os-c3"><label class="btn" for="os-c3">Monthly</label>
      </div>
    </fieldset>
    <div class="pf-email">
      <label class="form-label" for="os-email">Email address</label>
      <input class="form-control" id="os-email" name="email" type="email" autocomplete="email" required aria-describedby="os-email-err" data-err="os-email-err"
        data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
      <p class="pf-err" id="os-email-err"></p>
    </div>
    <div class="form-check pf-consent">
      <input class="form-check-input" id="os-consent" name="consent" type="checkbox" required aria-describedby="os-consent-err os-consent-note" data-err="os-consent-err"
        data-missing="Tick the box to agree to receive Off Season by email.">
      <div><label class="form-check-label" for="os-consent">I agree to receive Off Season by email. I can unsubscribe at any time.</label>
        <p class="form-text" id="os-consent-note">See how we handle your data in our <a href="#privacy">privacy notice</a>.</p>
        <p class="pf-err" id="os-consent-err"></p></div>
    </div>
    <button class="btn pf-btn w-100 nf-wide" type="submit">Subscribe</button>
  </form>
  <div class="pf-done" id="os-done" tabindex="-1" hidden>
    <span class="pf-done-ico" aria-hidden="true"><svg><use href="#pf-i-mail"/></svg></span>
    <h3>Nearly there</h3>
    <p>Open the email we sent to <b data-email>you</b> and confirm. Your picks are saved.</p>
  </div>
</div></section>
</div>
</div>
CSS
/* Bootstrap .card, .form-check-input, .btn-check + .btn for chips and the segmented .btn-group, .form-control and .btn,
   recoloured through Bootstrap's CSS variables. Every group is a fieldset with a legend; :has(:checked) lights a card. */
.pf{--pf-ink:#1b1a2e;--pf-muted:#545270;--pf-line:#7c7a96;--pf-soft-line:#dddbea;--pf-accent:#4338ca;--pf-accent-dark:#3730a3;--pf-tint:#eeedfd;
  --pf-ring:rgba(67,56,202,.24);--pf-error:#b42318;
  --bs-card-border-width:0;--bs-card-border-radius:20px;--bs-card-spacer-x:28px;--bs-card-spacer-y:28px;
  color:var(--pf-ink);font-size:.92rem;line-height:1.5;box-shadow:0 1px 2px rgba(27,26,46,.06),0 20px 44px -26px rgba(27,26,46,.4)}
.pf p,.pf h3,.pf legend{margin:0}
.pf a{color:var(--pf-accent-dark);font-weight:600;text-underline-offset:2px}
.pf a:focus-visible{outline:2px solid var(--pf-accent);outline-offset:2px;border-radius:3px}
.pf-head h3{font-size:1.3rem;font-weight:750;letter-spacing:-.02em;line-height:1.25}
.pf-head p{color:var(--pf-muted);margin-top:4px}
.pf .pf-set{min-width:0;margin-top:20px}
.pf .pf-set legend{float:none;width:auto;font-weight:700;font-size:.95rem;line-height:1.5;padding:0}
.pf .form-text{font-size:.8rem;color:var(--pf-muted);margin-top:2px}
.pf .pf-err{display:flex;gap:7px;align-items:flex-start;font-size:.82rem;font-weight:600;color:var(--pf-error);margin-top:8px}
.pf .pf-err:empty{display:none}
.pf .pf-err::before{content:"!";flex:none;display:grid;place-items:center;width:16px;height:16px;margin-top:2px;border-radius:50%;background:var(--pf-error);color:#fff;font-size:.7rem;font-weight:800}
.pf .form-check-input{--bs-form-check-bg:#fff;flex:none;width:20px;height:20px;margin:0;border:1.5px solid var(--pf-line);background-size:13px;cursor:pointer;transition:background-color .15s,border-color .15s}
.pf .form-check-input[type=checkbox]{border-radius:6px}
.pf .form-check-input:checked{background-color:var(--pf-accent);border-color:var(--pf-accent)}
.pf .form-check-input[type=radio]:checked{--bs-form-check-bg-image:radial-gradient(circle,#fff 0 3.5px,transparent 4px);background-size:auto}
.pf .form-check-input:focus{box-shadow:none;border-color:var(--pf-line)}
.pf .form-check-input:checked:focus{border-color:var(--pf-accent)}
.pf .form-check-input:focus-visible{outline:2px solid var(--pf-accent);outline-offset:2px}
.pf .form-check-input.is-invalid{border-color:var(--pf-error)}
.pf .pf-email{margin-top:20px}
.pf .form-label{display:block;font-weight:650;font-size:.86rem;margin-bottom:6px}
.pf .form-control{flex:1 1 0;min-width:0;font-size:inherit;line-height:inherit;color:var(--pf-ink);border:1.5px solid var(--pf-line);border-radius:10px;padding:10px 13px;transition:border-color .15s,box-shadow .15s}
.pf .form-control:focus{border-color:var(--pf-accent);box-shadow:0 0 0 4px var(--pf-ring)}
.pf .form-control.is-invalid{border-color:var(--pf-error);background-image:none;padding-right:13px}
.pf .pf-btn{--bs-btn-color:#fff;--bs-btn-bg:var(--pf-accent);--bs-btn-border-color:var(--pf-accent);--bs-btn-hover-color:#fff;--bs-btn-hover-bg:var(--pf-accent-dark);
  --bs-btn-hover-border-color:var(--pf-accent-dark);--bs-btn-active-color:#fff;--bs-btn-active-bg:var(--pf-accent-dark);--bs-btn-active-border-color:var(--pf-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}
.pf .pf-btn:focus-visible{outline:2px solid var(--pf-accent);outline-offset:3px}
.pf .nf-wide{margin-top:18px}
.pf .pf-done{outline:none;text-align:center;padding:10px 0 4px}
.pf .pf-done[hidden],.pf form[hidden]{display:none}
.pf .pf-done:focus-visible{outline:2px solid var(--pf-accent);outline-offset:4px;border-radius:12px}
.pf-done-ico{display:grid;place-items:center;width:48px;height:48px;margin:0 auto 10px;border-radius:50%;background:var(--pf-tint);color:var(--pf-accent)}
.pf-done-ico svg{width:24px;height:24px}
.pf .pf-done h3{font-size:1.15rem;font-weight:750}
.pf .pf-done p{color:var(--pf-muted);margin-top:4px}
.pf .pf-done b{color:var(--pf-ink);overflow-wrap:anywhere}

/* 1. Topic cards */
.pf-topics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:12px}
.pf-topic{position:relative;display:grid;grid-template-columns:auto 1fr;gap:4px 14px;align-items:start;padding:16px 16px 14px;border:1.5px solid var(--pf-soft-line);border-radius:14px;
  cursor:pointer;transition:border-color .15s,background-color .15s,box-shadow .15s}
.pf-topic:hover{border-color:#b9b6d6}
.pf-topic:has(:checked){border-color:var(--pf-accent);background:var(--pf-tint)}
.pf-topic:has(:focus-visible){box-shadow:0 0 0 4px var(--pf-ring)}
.pf-topic .form-check-input{position:absolute;top:14px;right:14px}
.pf-ico{grid-row:span 2;display:grid;place-items:center;width:42px;height:42px;border-radius:12px;background:var(--pf-tint);color:var(--pf-accent)}
.pf-topic:has(:checked) .pf-ico{background:var(--pf-accent);color:#fff}
.pf-ico svg{width:22px;height:22px}
.pf-topic b{font-size:.96rem;padding-right:28px}
.pf-topic small{display:block;font-size:.82rem;color:var(--pf-muted)}
.pf-topic .badge{grid-column:2;justify-self:start;margin-top:6px;--bs-badge-font-size:.72rem;--bs-badge-font-weight:700;--bs-badge-padding-x:9px;--bs-badge-padding-y:2px;
  --bs-badge-color:#3730a3;background:#e3e1fb;line-height:1.5}

/* 2. Frequency radio cards */
.pf-freqs{display:grid;gap:10px;margin-top:12px}
.pf-opt{display:flex;align-items:center;gap:12px;padding:13px 14px;border:1.5px solid var(--pf-soft-line);border-radius:12px;cursor:pointer;transition:border-color .15s,background-color .15s,box-shadow .15s}
.pf-opt:hover{border-color:#b9b6d6}
.pf-opt:has(:checked){border-color:var(--pf-accent);background:var(--pf-tint)}
.pf-opt:has(:focus-visible){box-shadow:0 0 0 4px var(--pf-ring)}
.pf-opt-text{flex:1;min-width:0}
.pf-opt b{display:block;font-size:.94rem}
.pf-opt small{font-size:.8rem;color:var(--pf-muted)}
.pf-read{flex:none;font-size:.76rem;font-weight:600;color:var(--pf-muted);white-space:nowrap}

/* 3. Chips (.btn-check + .btn), segmented .btn-group and consent */
.pf-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.pf .pf-chip{--bs-btn-color:var(--pf-ink);--bs-btn-border-color:var(--pf-line);--bs-btn-hover-color:var(--pf-ink);--bs-btn-hover-bg:transparent;--bs-btn-hover-border-color:var(--pf-accent);
  --bs-btn-active-color:#fff;--bs-btn-active-bg:var(--pf-accent);--bs-btn-active-border-color:var(--pf-accent);--bs-btn-focus-box-shadow:none;--bs-btn-font-size:.86rem;
  --bs-btn-font-weight:600;--bs-btn-line-height:1.5;--bs-btn-padding-x:13px;--bs-btn-padding-y:7px;--bs-btn-border-width:1.5px;--bs-btn-border-radius:999px;
  display:inline-flex;align-items:center;gap:6px;transition:background-color .15s,border-color .15s,color .15s}
.pf .pf-chip svg{width:14px;height:14px;display:none}
.pf .btn-check:checked+.pf-chip svg{display:block}
.pf .btn-check:focus-visible+.btn{outline:2px solid var(--pf-accent);outline-offset:2px;box-shadow:none}
.pf .pf-seg{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin-top:10px;padding:4px;background:#f0eff7;border:1px solid var(--pf-soft-line);border-radius:12px}
.pf .pf-seg .btn{--bs-btn-color:var(--pf-muted);--bs-btn-hover-color:var(--pf-ink);--bs-btn-active-color:var(--pf-ink);--bs-btn-active-bg:#fff;--bs-btn-active-border-color:transparent;
  --bs-btn-font-size:.86rem;--bs-btn-font-weight:600;--bs-btn-line-height:1.5;--bs-btn-padding-x:6px;--bs-btn-padding-y:8px;--bs-btn-border-width:0;--bs-btn-focus-box-shadow:none;
  border-radius:9px!important;margin:0!important;transition:background-color .15s,color .15s,box-shadow .15s}
.pf .pf-seg .btn-check:checked+.btn{box-shadow:0 1px 3px rgba(27,26,46,.18)}
.pf .pf-seg .btn-check:focus-visible+.btn{outline-offset:1px}
.pf .pf-consent{display:flex;gap:10px;align-items:flex-start;margin-top:16px;font-size:.84rem;padding:0;min-height:0}
.pf .pf-consent .form-check-input{float:none;margin:1px 0 0}
.pf .pf-consent .form-check-label{cursor:pointer}

@media (max-width:560px){.pf-topics{grid-template-columns:1fr}}
@media (max-width:420px){.pf{--bs-card-spacer-x:18px;--bs-card-spacer-y:22px}.pf .pf-row{flex-direction:column}}
@media (prefers-reduced-motion:reduce){.pf .form-check-input,.pf .form-control,.pf .pf-btn,.pf-topic,.pf-opt,.pf .pf-chip,.pf .pf-seg .btn{transition:none}}
JavaScript
// Front-end demo only: nothing is sent. On submit the script checks each field or fieldset that has data-err
// (a fieldset needs at least one ticked box), writes the message into the element it points to with
// aria-describedby, and moves focus to the first problem. A valid submit swaps in the success state.
// Without JavaScript the browser still checks the required email and consent fields.
(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); });
    });
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="pf-i-pan" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="13" r="7"/><path d="M17 13h5M7 4c0 1.5 1 1.5 1 3M11 3c0 1.5 1 1.5 1 3"/></symbol>
  <symbol id="pf-i-bread" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 11a4 4 0 0 1 1-7.8C8 3 9.5 4 12 4s4-1 6-.8A4 4 0 0 1 19 11v8a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"/><path d="M9 9l2 2M13 9l2 2"/></symbol>
  <symbol id="pf-i-glass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M7 3h10l-.5 6a4.5 4.5 0 0 1-9 0zM12 13.5V21M8 21h8M7.3 7h9.4"/></symbol>
  <symbol id="pf-i-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12V4a1 1 0 0 1 1-1h8l9 9-9 9z"/><circle cx="8" cy="8" r="1.5"/></symbol>
  <symbol id="pf-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="pf-i-tick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
</svg>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#59577a] uppercase">Topic cards</span>
<section class="rounded-[20px] bg-white p-7 text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(27,26,46,.06),0_20px_44px_-26px_rgba(27,26,46,.4)] max-[420px]:px-[18px] max-[420px]:py-[22px]" aria-labelledby="pn-title">
  <form action="#" method="post" data-nf="pn-done">
    <div><h3 class="text-[1.3rem] leading-[1.25] font-[750] tracking-[-.02em]" id="pn-title">Pantry & Flame newsletters</h3><p class="mt-1 text-pf-muted">Choose the emails you want. You can change this from any email we send.</p></div>
    <fieldset class="mt-5 min-w-0" aria-describedby="pn-topics-hint pn-topics-err" data-err="pn-topics-err" data-missing="Choose at least one newsletter.">
      <legend class="text-[.95rem] font-bold">What would you like to get?</legend>
      <p class="mt-0.5 text-[.8rem] text-pf-muted" id="pn-topics-hint">Pick one or more.</p>
      <div class="mt-3 grid grid-cols-2 gap-3 max-[560px]:grid-cols-1">
        <label class="group relative grid cursor-pointer grid-cols-[auto_1fr] items-start gap-x-3.5 gap-y-1 rounded-[14px] border-[1.5px] border-pf-soft px-4 pt-4 pb-3.5 transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="absolute top-3.5 right-3.5 m-0 size-5 flex-none cursor-pointer appearance-none rounded-md border-[1.5px] border-pf-line bg-white bg-size-[13px] bg-center bg-no-repeat transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="checkbox" name="topics" value="weeknight" checked>
          <span class="row-span-2 grid size-[42px] place-items-center rounded-xl bg-pf-tint text-pf-accent group-has-checked:bg-pf-accent group-has-checked:text-white" aria-hidden="true"><svg class="size-[22px]"><use href="#pf-i-pan"/></svg></span><b class="pr-7 text-[.96rem]">Weeknight Dinners</b>
          <small class="block text-[.82rem] text-pf-muted">Three 30-minute recipes and the shopping list for them.</small><span class="col-start-2 mt-1.5 justify-self-start rounded-full bg-[#e3e1fb] px-[9px] py-0.5 text-[.72rem] font-bold text-pf-accent-dark">Mondays</span></label>
        <label class="group relative grid cursor-pointer grid-cols-[auto_1fr] items-start gap-x-3.5 gap-y-1 rounded-[14px] border-[1.5px] border-pf-soft px-4 pt-4 pb-3.5 transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="absolute top-3.5 right-3.5 m-0 size-5 flex-none cursor-pointer appearance-none rounded-md border-[1.5px] border-pf-line bg-white bg-size-[13px] bg-center bg-no-repeat transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="checkbox" name="topics" value="baking">
          <span class="row-span-2 grid size-[42px] place-items-center rounded-xl bg-pf-tint text-pf-accent group-has-checked:bg-pf-accent group-has-checked:text-white" aria-hidden="true"><svg class="size-[22px]"><use href="#pf-i-bread"/></svg></span><b class="pr-7 text-[.96rem]">The Proving Drawer</b>
          <small class="block text-[.82rem] text-pf-muted">Bread and baking, with timings that fit around work.</small><span class="col-start-2 mt-1.5 justify-self-start rounded-full bg-[#e3e1fb] px-[9px] py-0.5 text-[.72rem] font-bold text-pf-accent-dark">Fridays</span></label>
        <label class="group relative grid cursor-pointer grid-cols-[auto_1fr] items-start gap-x-3.5 gap-y-1 rounded-[14px] border-[1.5px] border-pf-soft px-4 pt-4 pb-3.5 transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="absolute top-3.5 right-3.5 m-0 size-5 flex-none cursor-pointer appearance-none rounded-md border-[1.5px] border-pf-line bg-white bg-size-[13px] bg-center bg-no-repeat transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="checkbox" name="topics" value="wine">
          <span class="row-span-2 grid size-[42px] place-items-center rounded-xl bg-pf-tint text-pf-accent group-has-checked:bg-pf-accent group-has-checked:text-white" aria-hidden="true"><svg class="size-[22px]"><use href="#pf-i-glass"/></svg></span><b class="pr-7 text-[.96rem]">Cellar Notes</b>
          <small class="block text-[.82rem] text-pf-muted">Six bottles under £15 and what to cook with them.</small><span class="col-start-2 mt-1.5 justify-self-start rounded-full bg-[#e3e1fb] px-[9px] py-0.5 text-[.72rem] font-bold text-pf-accent-dark">Monthly</span></label>
        <label class="group relative grid cursor-pointer grid-cols-[auto_1fr] items-start gap-x-3.5 gap-y-1 rounded-[14px] border-[1.5px] border-pf-soft px-4 pt-4 pb-3.5 transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="absolute top-3.5 right-3.5 m-0 size-5 flex-none cursor-pointer appearance-none rounded-md border-[1.5px] border-pf-line bg-white bg-size-[13px] bg-center bg-no-repeat transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="checkbox" name="topics" value="deals">
          <span class="row-span-2 grid size-[42px] place-items-center rounded-xl bg-pf-tint text-pf-accent group-has-checked:bg-pf-accent group-has-checked:text-white" aria-hidden="true"><svg class="size-[22px]"><use href="#pf-i-tag"/></svg></span><b class="pr-7 text-[.96rem]">Kit Deals</b>
          <small class="block text-[.82rem] text-pf-muted">Tested kitchen kit when it is genuinely discounted.</small><span class="col-start-2 mt-1.5 justify-self-start rounded-full bg-[#e3e1fb] px-[9px] py-0.5 text-[.72rem] font-bold text-pf-accent-dark">Only when there's one</span></label>
      </div>
      <p class="mt-2 flex items-start gap-[7px] text-[.82rem] font-semibold text-pf-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-pf-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="pn-topics-err"></p>
    </fieldset>
    <div class="mt-5">
      <label class="mb-1.5 block text-[.86rem] font-[650]" for="pn-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-pf-line bg-white px-[13px] py-2.5 text-pf-ink transition-[border-color,box-shadow] duration-150 focus:border-pf-accent focus:shadow-[0_0_0_4px_rgba(67,56,202,.24)] focus:outline-none [&.is-invalid]:border-pf-error motion-reduce:transition-none max-[420px]:basis-auto" id="pn-email" name="email" type="email" autocomplete="email" required aria-describedby="pn-email-err" data-err="pn-email-err"
          data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="flex-none cursor-pointer rounded-[10px] bg-pf-accent px-[18px] py-2.5 font-[650] text-white transition-colors duration-150 hover:bg-pf-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-pf-accent motion-reduce:transition-none" type="submit">Subscribe</button>
      </div>
      <p class="mt-2 flex items-start gap-[7px] text-[.82rem] font-semibold text-pf-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-pf-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="pn-email-err"></p>
    </div>
  </form>
  <div class="px-0 pt-2.5 pb-1 text-center focus-visible:rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-pf-accent" id="pn-done" tabindex="-1" hidden>
    <span class="mx-auto mb-2.5 grid size-12 place-items-center rounded-full bg-pf-tint text-pf-accent" aria-hidden="true"><svg class="size-6"><use href="#pf-i-mail"/></svg></span>
    <h3 class="text-[1.15rem] font-[750]">Confirm and you're set</h3>
    <p class="mt-1 text-pf-muted">We sent one confirmation email to <b class="wrap-anywhere text-pf-ink" data-email>you</b> covering every newsletter you picked.</p>
  </div>
</section>
</div>

<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-[#59577a] uppercase">Frequency radio cards</span>
<section class="rounded-[20px] bg-white p-7 text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(27,26,46,.06),0_20px_44px_-26px_rgba(27,26,46,.4)] max-[420px]:px-[18px] max-[420px]:py-[22px]" aria-labelledby="cb-title">
  <form action="#" method="post" data-nf="cb-done">
    <div><h3 class="text-[1.3rem] leading-[1.25] font-[750] tracking-[-.02em]" id="cb-title">Circuit Brief</h3><p class="mt-1 text-pf-muted">Hardware and chip news without the hype.</p></div>
    <fieldset class="mt-5 min-w-0">
      <legend class="text-[.95rem] font-bold">How often?</legend>
      <div class="mt-3 grid gap-2.5">
        <label class="flex cursor-pointer items-center gap-3 rounded-xl border-[1.5px] border-pf-soft px-3.5 py-[13px] transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="m-0 size-5 flex-none cursor-pointer appearance-none rounded-full border-[1.5px] border-pf-line bg-white transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-dot focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="radio" name="frequency" value="daily">
          <span class="min-w-0 flex-1"><b class="block text-[.94rem]">Daily briefing</b><small class="text-[.8rem] text-pf-muted">Weekdays at 7am</small></span><span class="flex-none text-[.76rem] font-semibold whitespace-nowrap text-pf-muted">5 min read</span></label>
        <label class="flex cursor-pointer items-center gap-3 rounded-xl border-[1.5px] border-pf-soft px-3.5 py-[13px] transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="m-0 size-5 flex-none cursor-pointer appearance-none rounded-full border-[1.5px] border-pf-line bg-white transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-dot focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="radio" name="frequency" value="weekly" checked>
          <span class="min-w-0 flex-1"><b class="block text-[.94rem]">Weekly roundup</b><small class="text-[.8rem] text-pf-muted">Saturday mornings</small></span><span class="flex-none text-[.76rem] font-semibold whitespace-nowrap text-pf-muted">12 min read</span></label>
        <label class="flex cursor-pointer items-center gap-3 rounded-xl border-[1.5px] border-pf-soft px-3.5 py-[13px] transition-[border-color,background-color,box-shadow] duration-150 hover:border-[#b9b6d6] has-checked:border-pf-accent has-checked:bg-pf-tint has-focus-visible:shadow-[0_0_0_4px_rgba(67,56,202,.24)] motion-reduce:transition-none"><input class="m-0 size-5 flex-none cursor-pointer appearance-none rounded-full border-[1.5px] border-pf-line bg-white transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-dot focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent motion-reduce:transition-none" type="radio" name="frequency" value="monthly">
          <span class="min-w-0 flex-1"><b class="block text-[.94rem]">Monthly highlights</b><small class="text-[.8rem] text-pf-muted">First Sunday of the month</small></span><span class="flex-none text-[.76rem] font-semibold whitespace-nowrap text-pf-muted">20 min read</span></label>
      </div>
    </fieldset>
    <div class="mt-5">
      <label class="mb-1.5 block text-[.86rem] font-[650]" for="cb-email">Work or personal 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-pf-line bg-white px-[13px] py-2.5 text-pf-ink transition-[border-color,box-shadow] duration-150 focus:border-pf-accent focus:shadow-[0_0_0_4px_rgba(67,56,202,.24)] focus:outline-none [&.is-invalid]:border-pf-error motion-reduce:transition-none max-[420px]:basis-auto" id="cb-email" name="email" type="email" autocomplete="email" required aria-describedby="cb-email-err" data-err="cb-email-err"
          data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
        <button class="flex-none cursor-pointer rounded-[10px] bg-pf-accent px-[18px] py-2.5 font-[650] text-white transition-colors duration-150 hover:bg-pf-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-pf-accent motion-reduce:transition-none" type="submit">Subscribe</button>
      </div>
      <p class="mt-2 flex items-start gap-[7px] text-[.82rem] font-semibold text-pf-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-pf-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="cb-email-err"></p>
    </div>
  </form>
  <div class="pt-2.5 pb-1 text-center focus-visible:rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-pf-accent" id="cb-done" tabindex="-1" hidden>
    <span class="mx-auto mb-2.5 grid size-12 place-items-center rounded-full bg-pf-tint text-pf-accent" aria-hidden="true"><svg class="size-6"><use href="#pf-i-mail"/></svg></span>
    <h3 class="text-[1.15rem] font-[750]">Check your inbox</h3>
    <p class="mt-1 text-pf-muted">Confirm <b class="wrap-anywhere text-pf-ink" data-email>your address</b> and your first Circuit Brief arrives on schedule.</p>
  </div>
</section>
</div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#59577a] uppercase">Interest chips with consent</span>
<section class="rounded-[20px] bg-white p-7 text-[.92rem] leading-normal shadow-[0_1px_2px_rgba(27,26,46,.06),0_20px_44px_-26px_rgba(27,26,46,.4)] max-[420px]:px-[18px] max-[420px]:py-[22px]" aria-labelledby="os-title">
  <form action="#" method="post" data-nf="os-done">
    <div><h3 class="text-[1.3rem] leading-[1.25] font-[750] tracking-[-.02em]" id="os-title">Off Season</h3><p class="mt-1 text-pf-muted">Quiet places, out of peak, at fair prices.</p></div>
    <fieldset class="mt-5 min-w-0" aria-describedby="os-int-err" data-err="os-int-err" data-missing="Choose at least one interest.">
      <legend class="text-[.95rem] font-bold">I'm interested in</legend>
      <div class="mt-3 flex flex-wrap gap-2">
        <label class="group relative inline-flex cursor-pointer items-center gap-1.5 rounded-full border-[1.5px] border-pf-line px-[13px] py-[7px] text-[.86rem] font-semibold transition-colors duration-150 hover:border-pf-accent has-checked:border-pf-accent has-checked:bg-pf-accent has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="checkbox" name="interests" value="rail" checked><svg class="hidden size-3.5 group-has-checked:block" aria-hidden="true"><use href="#pf-i-tick"/></svg>Rail journeys</label>
        <label class="group relative inline-flex cursor-pointer items-center gap-1.5 rounded-full border-[1.5px] border-pf-line px-[13px] py-[7px] text-[.86rem] font-semibold transition-colors duration-150 hover:border-pf-accent has-checked:border-pf-accent has-checked:bg-pf-accent has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="checkbox" name="interests" value="hiking" checked><svg class="hidden size-3.5 group-has-checked:block" aria-hidden="true"><use href="#pf-i-tick"/></svg>Hiking</label>
        <label class="group relative inline-flex cursor-pointer items-center gap-1.5 rounded-full border-[1.5px] border-pf-line px-[13px] py-[7px] text-[.86rem] font-semibold transition-colors duration-150 hover:border-pf-accent has-checked:border-pf-accent has-checked:bg-pf-accent has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="checkbox" name="interests" value="cities"><svg class="hidden size-3.5 group-has-checked:block" aria-hidden="true"><use href="#pf-i-tick"/></svg>City breaks</label>
        <label class="group relative inline-flex cursor-pointer items-center gap-1.5 rounded-full border-[1.5px] border-pf-line px-[13px] py-[7px] text-[.86rem] font-semibold transition-colors duration-150 hover:border-pf-accent has-checked:border-pf-accent has-checked:bg-pf-accent has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="checkbox" name="interests" value="food"><svg class="hidden size-3.5 group-has-checked:block" aria-hidden="true"><use href="#pf-i-tick"/></svg>Food trips</label>
        <label class="group relative inline-flex cursor-pointer items-center gap-1.5 rounded-full border-[1.5px] border-pf-line px-[13px] py-[7px] text-[.86rem] font-semibold transition-colors duration-150 hover:border-pf-accent has-checked:border-pf-accent has-checked:bg-pf-accent has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="checkbox" name="interests" value="islands"><svg class="hidden size-3.5 group-has-checked:block" aria-hidden="true"><use href="#pf-i-tick"/></svg>Islands</label>
        <label class="group relative inline-flex cursor-pointer items-center gap-1.5 rounded-full border-[1.5px] border-pf-line px-[13px] py-[7px] text-[.86rem] font-semibold transition-colors duration-150 hover:border-pf-accent has-checked:border-pf-accent has-checked:bg-pf-accent has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="checkbox" name="interests" value="family"><svg class="hidden size-3.5 group-has-checked:block" aria-hidden="true"><use href="#pf-i-tick"/></svg>With kids</label>
      </div>
      <p class="mt-2 flex items-start gap-[7px] text-[.82rem] font-semibold text-pf-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-pf-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="os-int-err"></p>
    </fieldset>
    <fieldset class="mt-5 min-w-0">
      <legend class="text-[.95rem] font-bold">Send it</legend>
      <div class="mt-2.5 grid grid-cols-3 gap-1 rounded-xl border border-pf-soft bg-[#f0eff7] p-1">
        <label class="relative cursor-pointer rounded-[9px] px-1.5 py-2 text-center text-[.86rem] font-semibold text-pf-muted transition-[background-color,color,box-shadow] duration-150 has-checked:bg-white has-checked:text-pf-ink has-checked:shadow-[0_1px_3px_rgba(27,26,46,.18)] has-focus-visible:outline-2 has-focus-visible:outline-offset-1 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="radio" name="cadence" value="weekly" checked>Weekly</label>
        <label class="relative cursor-pointer rounded-[9px] px-1.5 py-2 text-center text-[.86rem] font-semibold text-pf-muted transition-[background-color,color,box-shadow] duration-150 has-checked:bg-white has-checked:text-pf-ink has-checked:shadow-[0_1px_3px_rgba(27,26,46,.18)] has-focus-visible:outline-2 has-focus-visible:outline-offset-1 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="radio" name="cadence" value="fortnightly">Fortnightly</label>
        <label class="relative cursor-pointer rounded-[9px] px-1.5 py-2 text-center text-[.86rem] font-semibold text-pf-muted transition-[background-color,color,box-shadow] duration-150 has-checked:bg-white has-checked:text-pf-ink has-checked:shadow-[0_1px_3px_rgba(27,26,46,.18)] has-focus-visible:outline-2 has-focus-visible:outline-offset-1 has-focus-visible:outline-pf-accent motion-reduce:transition-none"><input class="sr-only" type="radio" name="cadence" value="monthly">Monthly</label>
      </div>
    </fieldset>
    <div class="mt-5">
      <label class="mb-1.5 block text-[.86rem] font-[650]" for="os-email">Email address</label>
      <input class="block w-full min-w-0 rounded-[10px] border-[1.5px] border-pf-line bg-white px-[13px] py-2.5 text-pf-ink transition-[border-color,box-shadow] duration-150 focus:border-pf-accent focus:shadow-[0_0_0_4px_rgba(67,56,202,.24)] focus:outline-none [&.is-invalid]:border-pf-error motion-reduce:transition-none" id="os-email" name="email" type="email" autocomplete="email" required aria-describedby="os-email-err" data-err="os-email-err"
        data-missing="Enter your email address." data-bad="Enter an email address with an @ and a domain, like [email protected].">
      <p class="mt-2 flex items-start gap-[7px] text-[.82rem] font-semibold text-pf-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-pf-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="os-email-err"></p>
    </div>
    <div class="mt-4 flex items-start gap-2.5 text-[.84rem]">
      <input class="mt-px size-5 flex-none cursor-pointer appearance-none rounded-md border-[1.5px] border-pf-line bg-white bg-size-[13px] bg-center bg-no-repeat transition-colors duration-150 checked:border-pf-accent checked:bg-pf-accent checked:bg-pf-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent [&.is-invalid]:border-pf-error motion-reduce:transition-none" id="os-consent" name="consent" type="checkbox" required aria-describedby="os-consent-err os-consent-note" data-err="os-consent-err"
        data-missing="Tick the box to agree to receive Off Season by email.">
      <div><label class="cursor-pointer" for="os-consent">I agree to receive Off Season by email. I can unsubscribe at any time.</label>
        <p class="mt-0.5 text-[.8rem] text-pf-muted" id="os-consent-note">See how we handle your data in our <a class="font-semibold text-pf-accent-dark underline underline-offset-2 focus-visible:rounded-[3px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pf-accent" href="#privacy">privacy notice</a>.</p>
        <p class="mt-2 flex items-start gap-[7px] text-[.82rem] font-semibold text-pf-error empty:hidden before:mt-0.5 before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-pf-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="os-consent-err"></p></div>
    </div>
    <button class="mt-[18px] block w-full cursor-pointer rounded-[10px] bg-pf-accent px-[18px] py-2.5 font-[650] text-white transition-colors duration-150 hover:bg-pf-accent-dark focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-pf-accent motion-reduce:transition-none" type="submit">Subscribe</button>
  </form>
  <div class="pt-2.5 pb-1 text-center focus-visible:rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-pf-accent" id="os-done" tabindex="-1" hidden>
    <span class="mx-auto mb-2.5 grid size-12 place-items-center rounded-full bg-pf-tint text-pf-accent" aria-hidden="true"><svg class="size-6"><use href="#pf-i-mail"/></svg></span>
    <h3 class="text-[1.15rem] font-[750]">Nearly there</h3>
    <p class="mt-1 text-pf-muted">Open the email we sent to <b class="wrap-anywhere text-pf-ink" data-email>you</b> and confirm. Your picks are saved.</p>
  </div>
</section>
</div>
</div>
CSS
@theme {
  --color-pf-ink: #1b1a2e;
  --color-pf-muted: #545270;
  --color-pf-line: #7c7a96;
  --color-pf-soft: #dddbea;
  --color-pf-accent: #4338ca;
  --color-pf-accent-dark: #3730a3;
  --color-pf-tint: #eeedfd;
  --color-pf-error: #b42318;
  --background-image-pf-tick: 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");
  --background-image-pf-dot: radial-gradient(circle, #fff 0 3.5px, transparent 4px);
}
JavaScript
// Front-end demo only: nothing is sent. On submit the script checks each field or fieldset that has data-err
// (a fieldset needs at least one ticked box), writes the message into the element it points to with
// aria-describedby, and moves focus to the first problem. A valid submit swaps in the success state.
// Without JavaScript the browser still checks the required email and consent fields.
(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); });
    });
  });
})();

About this set

Three sign-up forms that let people choose what they get and how often, instead of one undifferentiated list. Topic cards offers four newsletters from a cooking site as checkbox cards in a two-column grid, each with an icon, a one-line description and a tag for when it arrives; a card lights up when ticked, and the form asks for at least one before it will subscribe. Frequency radio cards lets a tech newsletter reader choose a daily briefing, a weekly roundup or monthly highlights, with the reading time for each. Interest chips with consent combines pill-shaped interest chips, a three-way segmented control for how often, an email field and a GDPR-style consent checkbox that starts unticked, with a link to the privacy notice. Every group is a fieldset with a legend and the cards and chips are labels around real inputs, so mouse, Space and arrow keys all behave natively. A vanilla script checks the email, the consent box and the at-least-one rule, writes each message into its linked element and focuses the first problem; a valid submit shows a single confirmation. Nothing is sent.

What’s included

  • Checkbox topic cards with an at-least-one rule
  • Radio cards for frequency with reading time
  • Interest chips and a segmented control from real inputs
  • Consent checkbox unticked by default, linked to the privacy notice
  • Bootstrap version uses .form-check-input, .btn-check chips and a .btn-group

Accessibility

Topics, frequency and interests are each a fieldset with a legend, and every option is a native checkbox or radio inside its label. Error messages are linked with aria-describedby; the email and consent inputs get aria-invalid, and an empty topic group moves focus to its first checkbox. Cards, chips and segments draw a focus ring through :has(:focus-visible), and transitions switch off under prefers-reduced-motion.

Customise it

Change --pf-accent, --pf-accent-dark, --pf-tint and --pf-ring on .pf for the selected colour, and --pf-soft-line for card borders. In Bootstrap the same variables recolour .form-check-input and the .btn-check chips; in Tailwind edit the pf-* colours and the pf-tick image in @theme.