Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="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.5"/><path d="m3.5 7 8.5 6 8.5-6"/></symbol>
  <symbol id="i-phone" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.5"/><path d="M11 18.5h2"/></symbol>
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.9 1.9 0 0 0 3.4 0"/></symbol>
  <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9.5" rx="2"/><path d="M8.5 11V7.5a3.5 3.5 0 0 1 7 0V11"/></symbol>
  <symbol id="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"/></symbol>
</svg>

<div><span class="lbl">Channels × categories matrix with labelled switches, digest and quiet hours</span>
<form class="ns" aria-labelledby="ns-title">
  <div class="ns-head"><h2 id="ns-title">Notification settings</h2><p>Choose where each kind of update reaches you. Changes apply to all Northwind workspaces.</p></div>
  <div class="ns-scroll">
  <table class="ns-table">
    <caption class="sr-only">Notification channels for each category</caption>
    <thead><tr><th scope="col" class="ns-cat">Notify me about</th><th scope="col" id="ns-col-email"><svg aria-hidden="true"><use href="#i-mail"/></svg>Email</th><th scope="col" id="ns-col-push"><svg aria-hidden="true"><use href="#i-phone"/></svg>Push</th><th scope="col" id="ns-col-inapp"><svg aria-hidden="true"><use href="#i-bell"/></svg>In-app</th></tr></thead>
    <tbody>
      <tr class="ns-group"><th colspan="4" scope="rowgroup" id="ns-g-collaboration">Collaboration</th></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-mentions">Mentions and replies</span><span class="ns-desc">When someone @mentions you or replies to your comment</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-email" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-push" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-comments">Comments on your tasks</span><span class="ns-desc">New comments on tasks you created or follow</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-email"><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-push" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-assigned">Assigned to you</span><span class="ns-desc">A task or review is assigned to you</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-email" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-push" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-status">Status changes</span><span class="ns-desc">A task you follow moves to another column</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-status ns-col-email"><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-status ns-col-push"><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-status ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
    </tbody>
    <tbody>
      <tr class="ns-group"><th colspan="4" scope="rowgroup" id="ns-g-account">Account</th></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-billing">Billing and invoices</span><span class="ns-desc">Receipts, failed payments and plan changes</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-email" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-push"><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-security">Security alerts</span><span class="ns-desc">New sign-ins and password changes. Email is always on.</span><span class="ns-lock" id="ns-security-note"><svg aria-hidden="true"><use href="#i-lock"/></svg>Required for account safety</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-security ns-col-email" aria-describedby="ns-security-note" checked disabled><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-security ns-col-push" checked><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-security ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-product">Product updates</span><span class="ns-desc">New features and tips, at most twice a month</span></th><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-product ns-col-email"><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-product ns-col-push"><span class="ns-track" aria-hidden="true"></span></label></td><td><label class="ns-sw"><input type="checkbox" role="switch" aria-labelledby="ns-product ns-col-inapp" checked><span class="ns-track" aria-hidden="true"></span></label></td></tr>
    </tbody>
  </table>
  </div>
  <div class="ns-more">
    <fieldset class="ns-field">
      <legend>Email frequency</legend>
      <div class="ns-seg">
        <label><input type="radio" name="ns-freq" value="instant" checked><span>Instantly</span></label>
        <label><input type="radio" name="ns-freq" value="hourly"><span>Hourly digest</span></label>
        <label><input type="radio" name="ns-freq" value="daily"><span>Daily digest</span></label>
      </div>
    </fieldset>
    <fieldset class="ns-field">
      <legend>Quiet hours</legend>
      <div class="ns-quiet">
        <label class="ns-sw ns-sw-text"><input type="checkbox" role="switch" checked><span class="ns-track" aria-hidden="true"></span><svg class="ns-moon" aria-hidden="true"><use href="#i-moon"/></svg>Pause push</label>
        <span class="ns-range"><label class="ns-time">From <select><option>20:00</option><option>21:00</option><option selected>22:00</option><option>23:00</option></select></label>
        <label class="ns-time">to <select><option>06:00</option><option selected>07:00</option><option>08:00</option><option>09:00</option></select></label></span>
      </div>
    </fieldset>
  </div>
  <div class="ns-foot"><span class="ns-hint">Security alerts by email can’t be turned off.</span><button class="ns-btn" type="reset">Reset</button><button class="ns-btn primary" type="submit">Save preferences</button></div>
</form></div>
CSS
/* Notification settings: a real <table> with column and row headers. Each switch is a checkbox with role="switch",
   named by aria-labelledby from its row and column header, e.g. "Mentions and replies Email". No JavaScript. */
.ns{--ns-brand:#4f46e5;--ns-ink:#0f172a;--ns-text:#475569;--ns-muted:#5b6474;--ns-line:#e2e8f0;
  margin:0;background:#fff;border:1px solid var(--ns-line);border-radius:18px;box-shadow:0 1px 2px rgba(15,23,42,.05),0 24px 48px -30px rgba(15,23,42,.3);color:var(--ns-text);font-size:.9rem}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.ns-head{padding:24px 24px 8px}
.ns-head h2{margin:0;font-size:1.25rem;letter-spacing:-.01em;color:var(--ns-ink)}
.ns-head p{margin:4px 0 0;max-width:560px}
.ns-scroll{overflow-x:auto;padding:0 12px}
.ns-table{width:100%;border-collapse:collapse}
.ns-table th,.ns-table td{padding:14px 12px;border-bottom:1px solid var(--ns-line);text-align:left;vertical-align:middle}
.ns-table thead th{padding-top:18px;padding-bottom:12px;font-size:.75rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--ns-muted)}
.ns-table thead th:not(.ns-cat){width:96px;text-align:center}
.ns-table thead svg{display:block;width:18px;height:18px;margin:0 auto 6px;color:var(--ns-brand)}
.ns-table td{text-align:center}
.ns-group th{padding:18px 12px 8px;font-size:.8125rem;font-weight:700;color:var(--ns-ink);background:none}
.ns-table tbody tr:not(.ns-group):hover{background:#f8fafc}
.ns-table tbody tr:last-child th,.ns-table tbody tr:last-child td{border-bottom:0}
.ns-table tbody+tbody .ns-group th{border-top:1px solid var(--ns-line)}
.ns-name{display:block;font-weight:600;color:var(--ns-ink)}
.ns-desc{display:block;margin-top:2px;font-size:.8125rem;font-weight:400;color:var(--ns-muted)}
.ns-lock{display:inline-flex;align-items:center;gap:5px;margin-top:6px;padding:2px 8px;border-radius:999px;background:#fef3c7;color:#92400e;font-size:.75rem;font-weight:600}
.ns-lock svg{width:12px;height:12px}
/* Switch: visually hidden checkbox + a drawn track */
.ns-sw{position:relative;display:inline-flex;align-items:center;gap:10px;vertical-align:middle;cursor:pointer}
.ns-sw input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.ns-track{position:relative;flex:none;width:40px;height:22px;border-radius:999px;background:#7c8aa0;transition:background .2s}
.ns-track::before{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(15,23,42,.3);transition:transform .2s}
.ns-sw input:checked+.ns-track{background:var(--ns-brand)}
.ns-sw input:checked+.ns-track::before{transform:translateX(18px)}
.ns-sw input:disabled+.ns-track{background:#a5b4fc;cursor:not-allowed}
.ns-sw input:focus-visible+.ns-track{outline:2px solid var(--ns-brand);outline-offset:3px}
/* Digest + quiet hours */
.ns-more{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;margin:8px 24px 0;padding:20px 0 4px;border-top:1px solid var(--ns-line)}
.ns-field{margin:0;padding:0;border:0;min-width:0}
.ns-field legend{margin-bottom:10px;padding:0;font-weight:650;color:var(--ns-ink)}
.ns-seg{display:inline-flex;flex-wrap:wrap;gap:4px;padding:4px;border-radius:12px;background:#f1f5f9}
.ns-seg label{position:relative;cursor:pointer}
.ns-seg input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.ns-seg span{display:block;padding:7px 12px;border-radius:9px;font-size:.8125rem;font-weight:600;color:var(--ns-text);transition:background .15s,color .15s}
.ns-seg input:checked+span{background:#fff;color:var(--ns-ink);box-shadow:0 1px 3px rgba(15,23,42,.14)}
.ns-seg input:focus-visible+span{outline:2px solid var(--ns-brand);outline-offset:1px}
.ns-quiet{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px}
.ns-sw-text{font-weight:600;color:var(--ns-ink)}
.ns-moon{width:16px;height:16px;margin-right:-4px;color:var(--ns-brand)}
.ns-range{display:inline-flex;flex-wrap:wrap;gap:10px}
.ns-time{display:inline-flex;align-items:center;gap:8px;color:var(--ns-text)}
.ns-time select{height:34px;padding:0 8px;border:1px solid #cbd5e1;border-radius:9px;background:#fff;color:var(--ns-ink);font:inherit;font-size:.875rem}
.ns-time select:focus-visible{outline:2px solid var(--ns-brand);outline-offset:2px}
.ns-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:10px;margin-top:20px;padding:16px 24px;border-top:1px solid var(--ns-line);background:#fafbfc;border-radius:0 0 18px 18px}
.ns-hint{margin-right:auto;font-size:.8125rem;color:var(--ns-muted)}
.ns-btn{height:38px;padding:0 16px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:var(--ns-ink);font:inherit;font-size:.875rem;font-weight:650;cursor:pointer}
.ns-btn:hover{background:#f1f5f9}
.ns-btn.primary{border-color:var(--ns-brand);background:var(--ns-brand);color:#fff}
.ns-btn.primary:hover{background:#4338ca}
.ns-btn:focus-visible{outline:2px solid var(--ns-brand);outline-offset:2px}
@media (max-width:700px){.ns-more{grid-template-columns:1fr}}
@media (max-width:600px){
  .ns-head{padding:20px 16px 4px}.ns-scroll{padding:0 4px}.ns-more{margin:8px 16px 0}.ns-foot{padding:14px 16px}
  .ns-table th,.ns-table td{padding:12px 6px}
  .ns-table thead th:not(.ns-cat){width:56px;font-size:.6875rem;letter-spacing:.02em}
  .ns-desc{display:none}
}
@media (prefers-reduced-motion:reduce){.ns-track,.ns-track::before,.ns-seg span{transition:none}}

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="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.5"/><path d="m3.5 7 8.5 6 8.5-6"/></symbol>
  <symbol id="i-phone" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.5"/><path d="M11 18.5h2"/></symbol>
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.9 1.9 0 0 0 3.4 0"/></symbol>
  <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9.5" rx="2"/><path d="M8.5 11V7.5a3.5 3.5 0 0 1 7 0V11"/></symbol>
  <symbol id="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"/></symbol>
</svg>

<div><span class="lbl">Channels × categories matrix with labelled switches, digest and quiet hours</span>
<form class="card ns" aria-labelledby="ns-title">
  <div class="ns-head"><h2 id="ns-title">Notification settings</h2><p>Choose where each kind of update reaches you. Changes apply to all Northwind workspaces.</p></div>
  <div class="table-responsive ns-scroll">
  <table class="table align-middle mb-0 ns-table">
    <caption class="visually-hidden">Notification channels for each category</caption>
    <thead><tr><th scope="col" class="ns-cat">Notify me about</th><th scope="col" id="ns-col-email"><svg aria-hidden="true"><use href="#i-mail"/></svg>Email</th><th scope="col" id="ns-col-push"><svg aria-hidden="true"><use href="#i-phone"/></svg>Push</th><th scope="col" id="ns-col-inapp"><svg aria-hidden="true"><use href="#i-bell"/></svg>In-app</th></tr></thead>
    <tbody>
      <tr class="ns-group"><th colspan="4" scope="rowgroup" id="ns-g-collaboration">Collaboration</th></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-mentions">Mentions and replies</span><span class="ns-desc">When someone @mentions you or replies to your comment</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-email" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-push" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-inapp" checked></div></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-comments">Comments on your tasks</span><span class="ns-desc">New comments on tasks you created or follow</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-email"></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-push" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-inapp" checked></div></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-assigned">Assigned to you</span><span class="ns-desc">A task or review is assigned to you</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-email" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-push" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-inapp" checked></div></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-status">Status changes</span><span class="ns-desc">A task you follow moves to another column</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-status ns-col-email"></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-status ns-col-push"></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-status ns-col-inapp" checked></div></td></tr>
    </tbody>
    <tbody>
      <tr class="ns-group"><th colspan="4" scope="rowgroup" id="ns-g-account">Account</th></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-billing">Billing and invoices</span><span class="ns-desc">Receipts, failed payments and plan changes</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-email" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-push"></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-inapp" checked></div></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-security">Security alerts</span><span class="ns-desc">New sign-ins and password changes. Email is always on.</span><span class="badge rounded-pill ns-lock d-inline-flex align-items-center gap-1" id="ns-security-note"><svg aria-hidden="true"><use href="#i-lock"/></svg>Required for account safety</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-security ns-col-email" aria-describedby="ns-security-note" checked disabled></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-security ns-col-push" checked></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-security ns-col-inapp" checked></div></td></tr>
      <tr><th scope="row"><span class="ns-name" id="ns-product">Product updates</span><span class="ns-desc">New features and tips, at most twice a month</span></th><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-product ns-col-email"></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-product ns-col-push"></div></td><td><div class="form-check form-switch d-inline-block m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" aria-labelledby="ns-product ns-col-inapp" checked></div></td></tr>
    </tbody>
  </table>
  </div>
  <div class="ns-more">
    <fieldset>
      <legend class="ns-legend">Email frequency</legend>
      <div class="btn-group ns-seg" role="group">
        <input type="radio" class="btn-check" name="ns-freq" id="ns-f1" value="instant" checked><label class="btn" for="ns-f1">Instantly</label>
        <input type="radio" class="btn-check" name="ns-freq" id="ns-f2" value="hourly"><label class="btn" for="ns-f2">Hourly digest</label>
        <input type="radio" class="btn-check" name="ns-freq" id="ns-f3" value="daily"><label class="btn" for="ns-f3">Daily digest</label>
      </div>
    </fieldset>
    <fieldset>
      <legend class="ns-legend">Quiet hours</legend>
      <div class="d-flex flex-wrap align-items-center column-gap-3 row-gap-2">
        <div class="form-check form-switch d-inline-flex align-items-center gap-2 m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" id="ns-quiet" checked><label class="form-check-label ns-sw-text d-inline-flex align-items-center gap-1" for="ns-quiet"><svg class="ns-moon" aria-hidden="true"><use href="#i-moon"/></svg>Pause push</label></div>
        <span class="d-inline-flex flex-wrap gap-2"><label class="ns-time d-inline-flex align-items-center gap-2">From <select class="form-select form-select-sm"><option>20:00</option><option>21:00</option><option selected>22:00</option><option>23:00</option></select></label>
        <label class="ns-time d-inline-flex align-items-center gap-2">to <select class="form-select form-select-sm"><option>06:00</option><option selected>07:00</option><option>08:00</option><option>09:00</option></select></label></span>
      </div>
    </fieldset>
  </div>
  <div class="ns-foot d-flex flex-wrap align-items-center justify-content-end gap-2"><span class="ns-hint me-auto">Security alerts by email can’t be turned off.</span><button class="btn btn-outline-secondary" type="reset">Reset</button><button class="btn btn-primary" type="submit">Save preferences</button></div>
</form></div>
CSS
/* Notification settings on Bootstrap: a .table with column and row headers, .form-switch checkboxes named by
   aria-labelledby from their row and column header (e.g. "Mentions and replies Email"), a .btn-check segmented
   control and .form-select. No JavaScript. */
.ns{--ns-brand:#4f46e5;--ns-ink:#0f172a;--ns-text:#475569;--ns-muted:#5b6474;--ns-line:#e2e8f0;
  --bs-card-border-color:var(--ns-line);--bs-card-border-radius:18px;--bs-card-color:var(--ns-text);box-shadow:0 1px 2px rgba(15,23,42,.05),0 24px 48px -30px rgba(15,23,42,.3);font-size:.9rem}
.ns-head{padding:24px 24px 8px}
.ns-head h2{margin:0;font-size:1.25rem;font-weight:700;letter-spacing:-.01em;color:var(--ns-ink)}
.ns-head p{margin:4px 0 0;max-width:560px}
.ns-scroll{padding:0 12px}
.ns-table{--bs-table-color:var(--ns-text);--bs-table-border-color:var(--ns-line);--bs-table-hover-bg:#f8fafc}
.ns-table>:not(caption)>*>*{padding:14px 12px;text-align:center}
.ns-table th[scope="row"],.ns-table .ns-cat,.ns-group th{text-align:left}
.ns-table thead th{padding-top:18px;padding-bottom:12px;font-size:.75rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--ns-muted)}
.ns-table thead th:not(.ns-cat){width:96px}
.ns-table thead svg{display:block;width:18px;height:18px;margin:0 auto 6px;color:var(--ns-brand)}
.ns-table>:not(caption)>.ns-group>th{padding:18px 12px 8px;font-size:.8125rem;font-weight:700;color:var(--ns-ink)}
.ns-table tbody tr:not(.ns-group):hover>*{background:#f8fafc}
.ns-table tbody:last-child tr:last-child>*{border-bottom:0}
.ns-name{display:block;font-weight:600;color:var(--ns-ink)}
.ns-desc{display:block;margin-top:2px;font-size:.8125rem;font-weight:400;color:var(--ns-muted)}
.ns-lock{--bs-badge-font-size:.75rem;--bs-badge-font-weight:600;--bs-badge-padding-x:8px;--bs-badge-padding-y:3px;--bs-badge-color:#92400e;margin-top:6px;background:#fef3c7;white-space:normal;text-align:left}
.ns-lock svg{width:12px;height:12px}
.ns .form-switch .form-check-input{width:40px;height:22px;border:0;background-color:#7c8aa0;cursor:pointer;
  --bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.9' fill='%23fff'/%3e%3c/svg%3e")}
.ns .form-switch .form-check-input:checked{background-color:var(--ns-brand)}
.ns .form-switch .form-check-input:disabled{background-color:#a5b4fc;opacity:1}
.ns .form-switch .form-check-input:focus{box-shadow:none}
.ns .form-switch .form-check-input:focus-visible{outline:2px solid var(--ns-brand);outline-offset:3px}
.ns-more{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;margin:8px 24px 0;padding:20px 0 4px;border-top:1px solid var(--ns-line)}
.ns-more fieldset{min-width:0}
@media (max-width:700px){.ns-more{grid-template-columns:1fr}}
.ns-more>fieldset{margin-top:0}
.ns-legend{margin-bottom:10px;font-size:.9rem;font-weight:650;color:var(--ns-ink)}
.ns-seg{flex-wrap:wrap;gap:4px;padding:4px;border-radius:12px;background:#f1f5f9}
.ns-seg>.btn{--bs-btn-padding-x:12px;--bs-btn-padding-y:7px;--bs-btn-font-size:.8125rem;--bs-btn-font-weight:600;--bs-btn-color:var(--ns-text);--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--ns-ink);
  --bs-btn-hover-border-color:transparent;--bs-btn-active-bg:#fff;--bs-btn-active-color:var(--ns-ink);--bs-btn-active-border-color:transparent;border-radius:9px!important;margin:0!important;flex:0 0 auto}
.ns-seg>.btn-check:checked+.btn{box-shadow:0 1px 3px rgba(15,23,42,.14)}
.ns-seg>.btn-check:focus-visible+.btn{outline:2px solid var(--ns-brand);outline-offset:1px;box-shadow:none}
.ns-sw-text{font-weight:600;color:var(--ns-ink);cursor:pointer}
.ns-moon{width:16px;height:16px;color:var(--ns-brand)}
.ns-time .form-select{height:34px;width:auto;border-color:#cbd5e1;border-radius:9px;color:var(--ns-ink);font-size:.875rem}
.ns-time .form-select:focus{border-color:var(--ns-brand);box-shadow:0 0 0 2px #fff,0 0 0 4px var(--ns-brand)}
.ns-foot{margin-top:20px;padding:16px 24px;border-top:1px solid var(--ns-line);background:#fafbfc;border-radius:0 0 18px 18px}
.ns-hint{font-size:.8125rem;color:var(--ns-muted)}
.ns-foot .btn{--bs-btn-font-size:.875rem;--bs-btn-font-weight:650;--bs-btn-border-radius:10px;--bs-btn-padding-x:16px;--bs-btn-focus-box-shadow:0 0 0 2px #fff,0 0 0 4px var(--ns-brand);height:38px}
.ns-foot .btn-primary{--bs-btn-bg:var(--ns-brand);--bs-btn-border-color:var(--ns-brand);--bs-btn-hover-bg:#4338ca;--bs-btn-hover-border-color:#4338ca;--bs-btn-active-bg:#3730a3}
.ns-foot .btn-outline-secondary{--bs-btn-color:var(--ns-ink);--bs-btn-border-color:#cbd5e1;--bs-btn-hover-color:var(--ns-ink);--bs-btn-hover-bg:#f1f5f9;--bs-btn-hover-border-color:#cbd5e1;--bs-btn-active-bg:#e2e8f0;--bs-btn-active-color:var(--ns-ink);background:#fff}
@media (max-width:600px){
  .ns-head{padding:20px 16px 4px}.ns-scroll{padding:0 4px}.ns-more{margin:8px 16px 0}.ns-foot{padding:14px 16px}
  .ns-table>:not(caption)>*>*{padding:12px 6px}
  .ns-table thead th:not(.ns-cat){width:56px;font-size:.6875rem;letter-spacing:.02em}
  .ns-desc{display:none}
}
@media (prefers-reduced-motion:reduce){.ns .form-switch .form-check-input{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="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.5"/><path d="m3.5 7 8.5 6 8.5-6"/></symbol>
  <symbol id="i-phone" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.5"/><path d="M11 18.5h2"/></symbol>
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.9 1.9 0 0 0 3.4 0"/></symbol>
  <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9.5" rx="2"/><path d="M8.5 11V7.5a3.5 3.5 0 0 1 7 0V11"/></symbol>
  <symbol id="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"/></symbol>
</svg>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Channels × categories matrix with labelled switches, digest and quiet hours</span>
<form class="m-0 rounded-[18px] border border-[#e2e8f0] bg-white text-[.9rem] text-[#475569] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-30px_rgba(15,23,42,.3)]" aria-labelledby="ns-title">
  <div class="px-6 pt-6 pb-2 max-[600px]:px-4 max-[600px]:pt-5 max-[600px]:pb-1"><h2 class="m-0 text-xl font-bold tracking-[-.01em] text-[#0f172a]" id="ns-title">Notification settings</h2><p class="mt-1 mb-0 max-w-[560px]">Choose where each kind of update reaches you. Changes apply to all Northwind workspaces.</p></div>
  <div class="overflow-x-auto px-3 max-[600px]:px-1">
  <table class="w-full border-collapse">
    <caption class="sr-only">Notification channels for each category</caption>
    <thead><tr><th class="border-b border-[#e2e8f0] px-3 pt-[18px] pb-3 text-left text-xs font-bold tracking-[.06em] text-[#5b6474] uppercase max-[600px]:px-1.5" scope="col">Notify me about</th><th class="w-24 border-b border-[#e2e8f0] px-3 pt-[18px] pb-3 text-center text-xs font-bold tracking-[.06em] text-[#5b6474] uppercase max-[600px]:w-14 max-[600px]:px-1.5 max-[600px]:text-[.6875rem] max-[600px]:tracking-[.02em]" scope="col" id="ns-col-email"><svg class="mx-auto mb-1.5 block size-[18px] text-[#4f46e5]" aria-hidden="true"><use href="#i-mail"/></svg>Email</th><th class="w-24 border-b border-[#e2e8f0] px-3 pt-[18px] pb-3 text-center text-xs font-bold tracking-[.06em] text-[#5b6474] uppercase max-[600px]:w-14 max-[600px]:px-1.5 max-[600px]:text-[.6875rem] max-[600px]:tracking-[.02em]" scope="col" id="ns-col-push"><svg class="mx-auto mb-1.5 block size-[18px] text-[#4f46e5]" aria-hidden="true"><use href="#i-phone"/></svg>Push</th><th class="w-24 border-b border-[#e2e8f0] px-3 pt-[18px] pb-3 text-center text-xs font-bold tracking-[.06em] text-[#5b6474] uppercase max-[600px]:w-14 max-[600px]:px-1.5 max-[600px]:text-[.6875rem] max-[600px]:tracking-[.02em]" scope="col" id="ns-col-inapp"><svg class="mx-auto mb-1.5 block size-[18px] text-[#4f46e5]" aria-hidden="true"><use href="#i-bell"/></svg>In-app</th></tr></thead>
    <tbody class="group/tb">
      <tr><th class="px-3 pt-[18px] pb-2 text-left text-[.8125rem] font-bold text-[#0f172a] border-b border-[#e2e8f0]" colspan="4" scope="rowgroup" id="ns-g-collaboration">Collaboration</th></tr>
      <tr class="hover:bg-[#f8fafc]"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-mentions">Mentions and replies</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">When someone @mentions you or replies to your comment</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-email" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-push" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-mentions ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
      <tr class="hover:bg-[#f8fafc]"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-comments">Comments on your tasks</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">New comments on tasks you created or follow</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-email"><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-push" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-comments ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
      <tr class="hover:bg-[#f8fafc]"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-assigned">Assigned to you</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">A task or review is assigned to you</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-email" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-push" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-assigned ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
      <tr class="hover:bg-[#f8fafc] group-last/tb:[&:last-child>*]:border-b-0"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-status">Status changes</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">A task you follow moves to another column</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-status ns-col-email"><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-status ns-col-push"><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-status ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
    </tbody>
    <tbody class="group/tb">
      <tr><th class="px-3 pt-[18px] pb-2 text-left text-[.8125rem] font-bold text-[#0f172a] border-t border-[#e2e8f0] border-b border-[#e2e8f0]" colspan="4" scope="rowgroup" id="ns-g-account">Account</th></tr>
      <tr class="hover:bg-[#f8fafc]"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-billing">Billing and invoices</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">Receipts, failed payments and plan changes</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-email" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-push"><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-billing ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
      <tr class="hover:bg-[#f8fafc]"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-security">Security alerts</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">New sign-ins and password changes. Email is always on.</span><span class="mt-1.5 inline-flex items-center gap-[5px] rounded-full bg-[#fef3c7] px-2 py-0.5 text-xs font-semibold text-[#92400e]" id="ns-security-note"><svg class="size-3" aria-hidden="true"><use href="#i-lock"/></svg>Required for account safety</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-security ns-col-email" aria-describedby="ns-security-note" checked disabled><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-security ns-col-push" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-security ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
      <tr class="hover:bg-[#f8fafc] group-last/tb:[&:last-child>*]:border-b-0"><th class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-left font-normal" scope="row"><span class="block font-semibold text-[#0f172a]" id="ns-product">Product updates</span><span class="mt-0.5 block text-[.8125rem] text-[#5b6474] max-[600px]:hidden">New features and tips, at most twice a month</span></th><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-product ns-col-email"><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-product ns-col-push"><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td><td class="border-b border-[#e2e8f0] px-3 py-3.5 align-middle max-[600px]:px-1.5 max-[600px]:py-3 text-center"><label class="relative inline-flex cursor-pointer align-middle"><input class="peer sr-only" type="checkbox" role="switch" aria-labelledby="ns-product ns-col-inapp" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span></label></td></tr>
    </tbody>
  </table>
  </div>
  <div class="mx-6 mt-2 grid grid-cols-2 gap-5 border-t border-[#e2e8f0] pt-5 pb-1 max-[700px]:grid-cols-1 max-[600px]:mx-4">
    <fieldset class="m-0 min-w-0 border-0 p-0">
      <legend class="mb-2.5 p-0 font-[650] text-[#0f172a]">Email frequency</legend>
      <div class="inline-flex flex-wrap gap-1 rounded-xl bg-[#f1f5f9] p-1">
        <label class="relative cursor-pointer"><input class="peer sr-only" type="radio" name="ns-freq" value="instant" checked><span class="block rounded-[9px] px-3 py-[7px] text-[.8125rem] font-semibold text-[#475569] transition-colors duration-150 peer-checked:bg-white peer-checked:text-[#0f172a] peer-checked:shadow-[0_1px_3px_rgba(15,23,42,.14)] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Instantly</span></label>
        <label class="relative cursor-pointer"><input class="peer sr-only" type="radio" name="ns-freq" value="hourly"><span class="block rounded-[9px] px-3 py-[7px] text-[.8125rem] font-semibold text-[#475569] transition-colors duration-150 peer-checked:bg-white peer-checked:text-[#0f172a] peer-checked:shadow-[0_1px_3px_rgba(15,23,42,.14)] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Hourly digest</span></label>
        <label class="relative cursor-pointer"><input class="peer sr-only" type="radio" name="ns-freq" value="daily"><span class="block rounded-[9px] px-3 py-[7px] text-[.8125rem] font-semibold text-[#475569] transition-colors duration-150 peer-checked:bg-white peer-checked:text-[#0f172a] peer-checked:shadow-[0_1px_3px_rgba(15,23,42,.14)] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Daily digest</span></label>
      </div>
    </fieldset>
    <fieldset class="m-0 min-w-0 border-0 p-0">
      <legend class="mb-2.5 p-0 font-[650] text-[#0f172a]">Quiet hours</legend>
      <div class="flex flex-wrap items-center gap-x-3.5 gap-y-2.5">
        <label class="relative inline-flex cursor-pointer items-center gap-2.5 font-semibold text-[#0f172a]"><input class="peer sr-only" type="checkbox" role="switch" checked><span class="relative h-[22px] w-10 flex-none rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-disabled:cursor-not-allowed peer-disabled:bg-[#a5b4fc] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-[#4f46e5] before:absolute before:top-[3px] before:left-[3px] before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-[18px] motion-reduce:transition-none motion-reduce:before:transition-none" aria-hidden="true"></span><svg class="-mr-1 size-4 text-[#4f46e5]" aria-hidden="true"><use href="#i-moon"/></svg>Pause push</label>
        <span class="inline-flex flex-wrap gap-2.5"><label class="inline-flex items-center gap-2">From <select class="h-[34px] rounded-[9px] border border-[#cbd5e1] bg-white px-2 text-sm text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]"><option>20:00</option><option>21:00</option><option selected>22:00</option><option>23:00</option></select></label>
        <label class="inline-flex items-center gap-2">to <select class="h-[34px] rounded-[9px] border border-[#cbd5e1] bg-white px-2 text-sm text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]"><option>06:00</option><option selected>07:00</option><option>08:00</option><option>09:00</option></select></label></span>
      </div>
    </fieldset>
  </div>
  <div class="mt-5 flex flex-wrap items-center justify-end gap-2.5 rounded-b-[18px] border-t border-[#e2e8f0] bg-[#fafbfc] px-6 py-4 max-[600px]:px-4 max-[600px]:py-3.5"><span class="mr-auto text-[.8125rem] text-[#5b6474]">Security alerts by email can’t be turned off.</span><button class="h-[38px] cursor-pointer rounded-[10px] border border-[#cbd5e1] bg-white px-4 text-sm font-[650] text-[#0f172a] hover:bg-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="reset">Reset</button><button class="h-[38px] cursor-pointer rounded-[10px] border border-[#4f46e5] bg-[#4f46e5] px-4 text-sm font-[650] text-white hover:bg-[#4338ca] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="submit">Save preferences</button></div>
</form></div>
CSS
/* Utilities only: no custom CSS needed for this set. */

About this set

A notification preferences form built as a real table: categories down the side, channels across the top, and a switch in every cell. Columns for Email, Push and In-app have small icons, rows are grouped under Collaboration (mentions and replies, comments, assignments, status changes) and Account (billing, security alerts, product updates), and each row has a one-line description of when it fires. Security alerts by email are locked on: that switch is disabled and points to a Required for account safety note. Below the table, Email frequency is a segmented choice between Instantly, Hourly digest and Daily digest, and Quiet hours pairs a Pause push switch with from and to times. The footer has Reset and Save preferences buttons. Every switch is a checkbox with role=”switch” named by aria-labelledby from its row and column headers, so a screen reader hears Mentions and replies Email rather than an unlabelled toggle, and the table semantics let people move by row and column. There is no JavaScript. Below 600px the descriptions hide and the channel columns narrow so the matrix still fits a phone.

What’s included

  • Real table with column, row and row-group headers
  • 21 switches named from their row and column, such as Mentions and replies Email
  • A locked switch explained with aria-describedby
  • Digest choice as radio buttons in a fieldset and legend
  • No JavaScript; Bootstrap .table, .form-switch and .btn-check

Accessibility

Each switch has role="switch" and takes its name from the category and channel headers through aria-labelledby, and the table has a caption, scope attributes and rowgroup headers. The disabled security switch is described by its Required for account safety note. Email frequency and quiet hours are fieldsets with legends, every control shows a focus outline on its visible track or button, and switch and segment transitions are off under prefers-reduced-motion.

Customise it

In plain CSS change --ns-brand, --ns-ink, --ns-text and --ns-line on .ns, and the track colour on .ns-track. In Bootstrap set the colours on .form-switch .form-check-input and the --bs-btn-* variables on the .ns-seg buttons; in Tailwind edit the hex values in the peer-checked: and bg- utilities.