Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-spark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/></symbol>
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="i-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></symbol>
  <symbol id="i-bookmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3.5h12v17l-6-4-6 4z"/></symbol>
  <symbol id="i-filter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5h18l-7 8.5V19l-4 2v-7.5z"/></symbol>
  <symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></symbol>
</svg>

<div><span class="lbl">“What’s new” popover with versions and change types</span>
<div class="frame">
  <div class="frame-bar"><b>Ledgerly</b>
    <div class="wn wn-host" data-wn>
      <button class="wn-trigger" type="button" aria-expanded="true" aria-controls="wn-panel" data-wn-trigger><svg aria-hidden="true"><use href="#i-spark"/></svg>What’s new<span class="wn-dot" data-wn-dot><span class="sr-only">, 2 unseen updates</span></span></button>
      <section class="wn-panel" id="wn-panel" aria-labelledby="wn-title" data-wn-panel>
        <div class="wn-head"><h2 id="wn-title">What’s new in Ledgerly</h2><button class="wn-x" type="button" aria-label="Close what’s new" data-wn-close><svg aria-hidden="true"><use href="#i-x"/></svg></button></div>
        <div class="wn-body">
          <div class="wn-hero">
            <span class="wn-ver">Version 4.2</span>
            <h3>Bulk edit invoices</h3>
            <p>Select up to 200 invoices and change due dates, tax rates or status in one go.</p>
            <a href="#">Try bulk edit<svg aria-hidden="true"><use href="#i-arrow"/></svg></a>
          </div>
          <article class="wn-rel is-new" data-wn-new>
            <h3>4.2 <time datetime="2026-10-14">14 Oct 2026</time><span class="wn-unseen" data-wn-unseen>Unseen</span></h3>
            <ul class="wn-changes">
              <li><span class="wn-tag new">New</span>Bulk edit for up to 200 invoices at once.</li>
              <li><span class="wn-tag imp">Improved</span>Search now finds invoices by amount, like “1,240”.</li>
            </ul>
          </article>
          <article class="wn-rel is-new" data-wn-new>
            <h3>4.1 <time datetime="2026-09-30">30 Sep 2026</time><span class="wn-unseen" data-wn-unseen>Unseen</span></h3>
            <ul class="wn-changes">
              <li><span class="wn-tag imp">Improved</span>The dashboard loads about 40% faster on large accounts.</li>
              <li><span class="wn-tag fix">Fixed</span>PDF exports keep your logo on every page.</li>
            </ul>
          </article>
          <article class="wn-rel">
            <h3>4.0 <time datetime="2026-09-02">2 Sep 2026</time></h3>
            <ul class="wn-changes">
              <li><span class="wn-tag new">New</span>Reports in more than one currency, converted daily.</li>
            </ul>
          </article>
        </div>
        <div class="wn-foot"><a class="wn-link" href="#">Read the full changelog</a><button class="wn-btn" type="button" data-wn-seen>Mark as seen</button></div>
        <p class="sr-only" role="status" data-wn-status></p>
      </section>
    </div>
  </div>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
</div></div>

<div><span class="lbl">Feature spotlight pointing at a new button</span>
<div class="frame short">
  <div class="frame-bar"><b>Invoices</b></div>
  <div class="wn" data-wn-spot-host>
    <div class="wn-tools">
      <div class="wn-anchor"><button class="wn-tool is-spot" type="button" id="wn-views" aria-describedby="wn-spot-text" data-wn-spot-target><svg aria-hidden="true"><use href="#i-bookmark"/></svg>Saved views</button>
      <section class="wn-spot" aria-labelledby="wn-spot-title" data-wn-spot>
        <h3 id="wn-spot-title"><span>New</span>Saved views</h3>
        <p id="wn-spot-text">Save any filter and sort as a view, then share it with your team.</p>
        <div class="wn-spot-foot"><small>Tip 1 of 1</small><a class="wn-btn" href="#">Learn more</a><button class="wn-btn primary" type="button" data-wn-spot-close>Got it</button></div>
      </section>
      </div>
      <button class="wn-tool" type="button"><svg aria-hidden="true"><use href="#i-filter"/></svg>Filter</button>
      <button class="wn-tool" type="button"><svg aria-hidden="true"><use href="#i-download"/></svg>Export</button>
    </div>
  </div>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i></div>
</div></div>
CSS
/* "What's new" popover: a button with an unseen dot opens a labelled panel of releases. Versions are headings,
   change types are text badges (New, Improved, Fixed), and "Mark as seen" clears the dot. */
.wn{--wn-brand:#4f46e5;--wn-ink:#0f172a;--wn-text:#475569;--wn-muted:#5b6474;--wn-line:#e2e8f0;position:relative;color:var(--wn-text);font-size:.875rem;line-height:1.5}
.wn [hidden]{display:none!important}
.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}
.wn-trigger{position:relative;display:inline-flex;align-items:center;gap:8px;height:38px;padding:0 14px 0 12px;border:1px solid #c7d2fe;border-radius:999px;background:#eef2ff;color:#3730a3;
  font:inherit;font-size:.875rem;font-weight:650;cursor:pointer}
.wn-trigger:hover,.wn-trigger[aria-expanded="true"]{background:#e0e7ff}
.wn-trigger svg{width:17px;height:17px}
.wn-dot{position:absolute;top:-2px;right:-2px;width:11px;height:11px;border:2px solid #fff;border-radius:50%;background:#e11d48}
.wn-panel{position:absolute;top:calc(100% + 12px);right:0;z-index:20;width:420px;max-width:calc(100vw - 32px);background:#fff;border:1px solid var(--wn-line);border-radius:18px;
  box-shadow:0 30px 60px -24px rgba(15,23,42,.38),0 4px 12px rgba(15,23,42,.06);transform-origin:top right;animation:wn-in .2s ease-out}
.wn-panel::before{content:"";position:absolute;top:-7px;right:44px;width:12px;height:12px;background:#fff;border-left:1px solid var(--wn-line);border-top:1px solid var(--wn-line);transform:rotate(45deg)}
.wn-head{display:flex;align-items:center;gap:10px;padding:16px 14px 12px 18px}
.wn-head h2{flex:1;margin:0;font-size:1rem;color:var(--wn-ink)}
.wn-x{display:grid;place-items:center;width:32px;height:32px;padding:0;border:0;border-radius:9px;background:transparent;color:#64748b;cursor:pointer}
.wn-x:hover{background:#f1f5f9;color:var(--wn-ink)}
.wn-x svg{width:15px;height:15px}
.wn-body{max-height:440px;overflow-y:auto;padding:0 18px 6px}
/* Featured release */
.wn-hero{position:relative;overflow:hidden;margin-bottom:6px;padding:18px;border-radius:14px;color:#e0e7ff;
  background:radial-gradient(120% 140% at 100% 0%,#7c3aed 0%,transparent 55%),linear-gradient(135deg,#312e81,#1e1b4b)}
.wn-hero::after{content:"";position:absolute;right:-28px;bottom:-34px;width:130px;height:130px;border-radius:28px;background:rgba(255,255,255,.07);transform:rotate(18deg)}
.wn-ver{display:inline-block;padding:2px 9px;border-radius:999px;background:rgba(255,255,255,.14);color:#fff;font-size:.75rem;font-weight:700;letter-spacing:.02em}
.wn-hero h3{margin:10px 0 4px;font-size:1.0625rem;color:#fff}
.wn-hero p{position:relative;z-index:1;margin:0 0 12px;max-width:300px}
.wn-hero a{position:relative;z-index:1;display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border-radius:9px;background:#fff;color:#312e81;font-weight:650;text-decoration:none}
.wn-hero a:hover{background:#eef2ff}
.wn-hero a svg{width:14px;height:14px}
/* Release list */
.wn-rel{position:relative;padding:16px 0 12px 18px;border-left:2px solid var(--wn-line);margin-left:6px}
.wn-rel::before{content:"";position:absolute;left:-7px;top:21px;width:12px;height:12px;border-radius:50%;background:#fff;border:2px solid #94a3b8}
.wn-rel.is-new::before{border-color:var(--wn-brand);background:var(--wn-brand);box-shadow:0 0 0 4px #e0e7ff}
.wn-rel h3{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin:0 0 8px;font-size:.9375rem;color:var(--wn-ink)}
.wn-rel time{font-size:.75rem;font-weight:500;color:var(--wn-muted)}
.wn-unseen{padding:1px 7px;border-radius:999px;background:#ffe4e6;color:#9f1239;font-size:.6875rem;font-weight:700}
.wn-changes{margin:0;padding:0;list-style:none;display:grid;gap:8px}
.wn-changes li{display:flex;align-items:flex-start;gap:9px}
.wn-tag{flex:none;min-width:70px;margin-top:1px;padding:1px 0;border-radius:6px;font-size:.6875rem;font-weight:700;text-align:center;letter-spacing:.02em}
.wn-tag.new{background:#dcfce7;color:#166534}
.wn-tag.imp{background:#e0e7ff;color:#3730a3}
.wn-tag.fix{background:#fef3c7;color:#92400e}
.wn-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;padding:12px 18px 14px;border-top:1px solid var(--wn-line)}
.wn-link{color:#4338ca;font-weight:650;text-decoration:none}
.wn-link:hover{text-decoration:underline}
.wn-btn{height:34px;padding:0 12px;border:1px solid #cbd5e1;border-radius:9px;background:#fff;color:var(--wn-ink);font:inherit;font-size:.8125rem;font-weight:650;cursor:pointer}
a.wn-btn{display:inline-grid;place-items:center;text-decoration:none}
.wn-btn:hover{background:#f1f5f9}
.wn-btn.primary{border-color:var(--wn-brand);background:var(--wn-brand);color:#fff}
.wn-btn.primary:hover{background:#4338ca}
.wn-btn:disabled{color:#64748b;background:#f8fafc;border-color:#e2e8f0;cursor:default}
/* Feature spotlight (coachmark) */
.wn-tools{display:flex;flex-wrap:wrap;gap:8px;padding:18px 20px}
.wn-tool{position:relative;display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 13px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:var(--wn-ink);font:inherit;font-size:.8125rem;font-weight:600;cursor:pointer}
.wn-tool svg{width:16px;height:16px}
.wn-tool.is-spot{border-color:var(--wn-brand);box-shadow:0 0 0 4px #e0e7ff}
.wn-tool.is-spot::after{content:"";position:absolute;inset:-4px;border-radius:13px;border:2px solid var(--wn-brand);animation:wn-pulse 1.8s ease-out 3}
.wn-anchor{position:relative;display:inline-flex}
.wn-spot{position:absolute;top:calc(100% + 14px);left:0;z-index:10;width:300px;max-width:calc(100vw - 64px);padding:16px 16px 14px;border-radius:14px;background:#1e1b4b;color:#c7d2fe;
  box-shadow:0 24px 48px -20px rgba(30,27,75,.6);animation:wn-in .2s ease-out}
.wn-spot::before{content:"";position:absolute;top:-6px;left:28px;width:12px;height:12px;background:#1e1b4b;transform:rotate(45deg)}
.wn-spot h3{display:flex;align-items:center;gap:8px;margin:0 0 4px;font-size:.9375rem;color:#fff}
.wn-spot h3 span{padding:1px 7px;border-radius:999px;background:#a5b4fc;color:#1e1b4b;font-size:.6875rem;font-weight:800}
.wn-spot p{margin:0 0 12px}
.wn-spot-foot{display:flex;align-items:center;gap:8px}
.wn-spot-foot small{margin-right:auto;font-size:.75rem;color:#a5b4fc}
.wn-spot .wn-btn{border-color:rgba(199,210,254,.35);background:transparent;color:#e0e7ff}
.wn-spot .wn-btn:hover{background:rgba(199,210,254,.12)}
.wn-spot .wn-btn.primary{border-color:#fff;background:#fff;color:#1e1b4b}
.wn-trigger:focus-visible,.wn-x:focus-visible,.wn-btn:focus-visible,.wn-link:focus-visible,.wn-hero a:focus-visible,.wn-tool:focus-visible{outline:2px solid var(--wn-brand);outline-offset:2px}
.wn-spot .wn-btn:focus-visible,.wn-hero a:focus-visible{outline-color:#a5b4fc}
@keyframes wn-in{from{opacity:0;transform:translateY(-6px) scale(.98)}}
@keyframes wn-pulse{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.25)}}
@media (max-width:520px){.wn-host{position:static}.wn-panel{left:0;right:0;width:auto;max-width:none}.wn-panel::before{display:none}.wn-tag{min-width:62px}}
@media (prefers-reduced-motion:reduce){.wn-panel,.wn-spot{animation:none}.wn-tool.is-spot::after{animation:none;opacity:0}}
JavaScript
(function () {
  // "What's new" popover
  document.querySelectorAll('[data-wn]').forEach(function (wn) {
    var trigger = wn.querySelector('[data-wn-trigger]'), panel = wn.querySelector('[data-wn-panel]');
    if (!trigger || !panel) return;
    var seen = wn.querySelector('[data-wn-seen]'), status = wn.querySelector('[data-wn-status]');
    function setOpen(open) { trigger.setAttribute('aria-expanded', open); panel.hidden = !open; }
    trigger.addEventListener('click', function () { setOpen(panel.hidden); });
    wn.querySelector('[data-wn-close]').addEventListener('click', function () { setOpen(false); trigger.focus(); });
    seen.addEventListener('click', function () {
      wn.querySelectorAll('[data-wn-unseen], [data-wn-dot]').forEach(function (el) { el.hidden = true; });
      wn.querySelectorAll('[data-wn-new]').forEach(function (el) { el.removeAttribute('data-wn-new'); el.classList.remove('is-new'); });
      seen.disabled = true; seen.textContent = 'All seen';
      status.textContent = 'Updates marked as seen';
    });
    wn.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !panel.hidden) { setOpen(false); trigger.focus(); } });
    document.addEventListener('click', function (e) { if (!panel.hidden && !wn.contains(e.target)) setOpen(false); });
  });

  // Feature spotlight: "Got it" closes the tip, removes the highlight and returns focus to the feature.
  document.querySelectorAll('[data-wn-spot-host]').forEach(function (host) {
    var spot = host.querySelector('[data-wn-spot]'), target = host.querySelector('[data-wn-spot-target]');
    function close() { spot.hidden = true; target.classList.remove('is-spot'); target.removeAttribute('aria-describedby'); target.focus(); }
    host.querySelector('[data-wn-spot-close]').addEventListener('click', close);
    host.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !spot.hidden) close(); });
  });
})();

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-spark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/></symbol>
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="i-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></symbol>
  <symbol id="i-bookmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3.5h12v17l-6-4-6 4z"/></symbol>
  <symbol id="i-filter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5h18l-7 8.5V19l-4 2v-7.5z"/></symbol>
  <symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></symbol>
</svg>

<div><span class="lbl">“What’s new” popover with versions and change types</span>
<div class="frame">
  <div class="frame-bar"><b>Ledgerly</b>
    <div class="wn wn-host" data-wn>
      <button class="btn wn-trigger" type="button" aria-expanded="true" aria-controls="wn-panel" data-wn-trigger><svg aria-hidden="true"><use href="#i-spark"/></svg>What’s new<span class="wn-dot" data-wn-dot><span class="visually-hidden">, 2 unseen updates</span></span></button>
      <section class="popover bs-popover-bottom wn-panel" id="wn-panel" aria-labelledby="wn-title" data-wn-panel>
        <div class="popover-arrow"></div>
        <div class="popover-header"><h2 id="wn-title">What’s new in Ledgerly</h2><button class="btn-close" type="button" aria-label="Close what’s new" data-wn-close></button></div>
        <div class="popover-body">
          <div class="wn-hero">
            <span class="badge rounded-pill wn-ver">Version 4.2</span>
            <h3>Bulk edit invoices</h3>
            <p>Select up to 200 invoices and change due dates, tax rates or status in one go.</p>
            <a class="btn" href="#">Try bulk edit<svg aria-hidden="true"><use href="#i-arrow"/></svg></a>
          </div>
          <article class="wn-rel is-new" data-wn-new>
            <h3>4.2 <time datetime="2026-10-14">14 Oct 2026</time><span class="badge rounded-pill wn-unseen" data-wn-unseen>Unseen</span></h3>
            <ul class="list-unstyled m-0 wn-changes">
              <li class="d-flex align-items-start gap-2"><span class="badge wn-tag new">New</span>Bulk edit for up to 200 invoices at once.</li>
              <li class="d-flex align-items-start gap-2"><span class="badge wn-tag imp">Improved</span>Search now finds invoices by amount, like “1,240”.</li>
            </ul>
          </article>
          <article class="wn-rel is-new" data-wn-new>
            <h3>4.1 <time datetime="2026-09-30">30 Sep 2026</time><span class="badge rounded-pill wn-unseen" data-wn-unseen>Unseen</span></h3>
            <ul class="list-unstyled m-0 wn-changes">
              <li class="d-flex align-items-start gap-2"><span class="badge wn-tag imp">Improved</span>The dashboard loads about 40% faster on large accounts.</li>
              <li class="d-flex align-items-start gap-2"><span class="badge wn-tag fix">Fixed</span>PDF exports keep your logo on every page.</li>
            </ul>
          </article>
          <article class="wn-rel">
            <h3>4.0 <time datetime="2026-09-02">2 Sep 2026</time></h3>
            <ul class="list-unstyled m-0 wn-changes">
              <li class="d-flex align-items-start gap-2"><span class="badge wn-tag new">New</span>Reports in more than one currency, converted daily.</li>
            </ul>
          </article>
        </div>
        <div class="wn-foot d-flex flex-wrap align-items-center justify-content-between gap-2"><a class="wn-link" href="#">Read the full changelog</a><button class="btn btn-outline-secondary" type="button" data-wn-seen>Mark as seen</button></div>
        <p class="visually-hidden" role="status" data-wn-status></p>
      </section>
    </div>
  </div>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
</div></div>

<div><span class="lbl">Feature spotlight pointing at a new button</span>
<div class="frame short">
  <div class="frame-bar"><b>Invoices</b></div>
  <div class="wn" data-wn-spot-host>
    <div class="wn-tools d-flex flex-wrap gap-2">
      <div class="wn-anchor"><button class="btn wn-tool is-spot" type="button" id="wn-views" aria-describedby="wn-spot-text" data-wn-spot-target><svg aria-hidden="true"><use href="#i-bookmark"/></svg>Saved views</button>
      <section class="popover bs-popover-bottom wn-spot" aria-labelledby="wn-spot-title" data-wn-spot>
        <div class="popover-arrow"></div>
        <div class="popover-body">
          <h3 id="wn-spot-title"><span class="badge rounded-pill">New</span>Saved views</h3>
          <p id="wn-spot-text">Save any filter and sort as a view, then share it with your team.</p>
          <div class="d-flex align-items-center gap-2"><small class="me-auto">Tip 1 of 1</small><a class="btn btn-outline-light" href="#">Learn more</a><button class="btn btn-light" type="button" data-wn-spot-close>Got it</button></div>
        </div>
      </section>
      </div>
      <button class="btn wn-tool" type="button"><svg aria-hidden="true"><use href="#i-filter"/></svg>Filter</button>
      <button class="btn wn-tool" type="button"><svg aria-hidden="true"><use href="#i-download"/></svg>Export</button>
    </div>
  </div>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i></div>
</div></div>
CSS
/* "What's new" on Bootstrap: static .popover.bs-popover-bottom panels (header, body, arrow) opened by a button,
   .badge change types and .btn actions. A few lines of script open, close and mark updates as seen. */
.wn{--wn-brand:#4f46e5;--wn-ink:#0f172a;--wn-text:#475569;--wn-muted:#5b6474;--wn-line:#e2e8f0;position:relative;color:var(--wn-text);font-size:.875rem;line-height:1.5}
.wn-trigger{--bs-btn-padding-x:14px;--bs-btn-font-size:.875rem;--bs-btn-font-weight:650;--bs-btn-color:#3730a3;--bs-btn-bg:#eef2ff;--bs-btn-border-color:#c7d2fe;--bs-btn-hover-color:#3730a3;--bs-btn-hover-bg:#e0e7ff;
  --bs-btn-hover-border-color:#c7d2fe;--bs-btn-active-color:#3730a3;--bs-btn-active-bg:#e0e7ff;--bs-btn-active-border-color:#c7d2fe;--bs-btn-border-radius:999px;--bs-btn-focus-box-shadow:0 0 0 2px #fff,0 0 0 4px var(--wn-brand);
  position:relative;display:inline-flex;align-items:center;gap:8px;height:38px;padding-left:12px}
.wn-trigger[aria-expanded="true"]{background:#e0e7ff}
.wn-trigger svg{width:17px;height:17px}
.wn-dot{position:absolute;top:-2px;right:-2px;width:11px;height:11px;border:2px solid #fff;border-radius:50%;background:#e11d48}
.wn-panel{--bs-popover-max-width:420px;--bs-popover-border-color:var(--wn-line);--bs-popover-border-radius:18px;--bs-popover-inner-border-radius:17px;--bs-popover-box-shadow:0 30px 60px -24px rgba(15,23,42,.38),0 4px 12px rgba(15,23,42,.06);
  --bs-popover-header-bg:#fff;--bs-popover-header-color:var(--wn-ink);--bs-popover-header-padding-x:18px;--bs-popover-body-padding-x:18px;--bs-popover-body-padding-y:0;--bs-popover-body-color:var(--wn-text);--bs-popover-font-size:.875rem;
  position:absolute;z-index:20;top:calc(100% + 12px);left:auto;right:0;width:420px;max-width:calc(100vw - 32px);box-shadow:var(--bs-popover-box-shadow);font-family:inherit;transform-origin:top right;animation:wn-in .2s ease-out}
.wn-panel .popover-arrow{left:auto;right:44px}
.wn-panel .popover-header{display:flex;align-items:center;gap:10px;padding:16px 14px 12px 18px;border-bottom:0}
.wn-panel .popover-header h2{flex:1;margin:0;font-size:1rem;font-weight:700}
.wn-panel .btn-close{--bs-btn-close-focus-shadow:0 0 0 2px var(--wn-brand);width:32px;height:32px;padding:0;border-radius:9px;background-size:11px}
.wn-panel .popover-body{max-height:440px;overflow-y:auto;padding-bottom:6px}
.wn-hero{position:relative;overflow:hidden;margin-bottom:6px;padding:18px;border-radius:14px;color:#e0e7ff;
  background:radial-gradient(120% 140% at 100% 0%,#7c3aed 0%,transparent 55%),linear-gradient(135deg,#312e81,#1e1b4b)}
.wn-hero::after{content:"";position:absolute;right:-28px;bottom:-34px;width:130px;height:130px;border-radius:28px;background:rgba(255,255,255,.07);transform:rotate(18deg)}
.wn-ver{--bs-badge-font-size:.75rem;--bs-badge-padding-x:9px;--bs-badge-padding-y:4px;background:rgba(255,255,255,.14);letter-spacing:.02em}
.wn-hero h3{margin:10px 0 4px;font-size:1.0625rem;font-weight:700;color:#fff}
.wn-hero p{position:relative;z-index:1;margin:0 0 12px;max-width:300px}
.wn-hero .btn{--bs-btn-padding-x:12px;--bs-btn-padding-y:7px;--bs-btn-font-size:.875rem;--bs-btn-font-weight:650;--bs-btn-color:#312e81;--bs-btn-bg:#fff;--bs-btn-border-color:#fff;--bs-btn-hover-color:#312e81;--bs-btn-hover-bg:#eef2ff;
  --bs-btn-hover-border-color:#eef2ff;--bs-btn-border-radius:9px;--bs-btn-focus-box-shadow:0 0 0 2px #a5b4fc;position:relative;z-index:1;display:inline-flex;align-items:center;gap:6px}
.wn-hero .btn svg{width:14px;height:14px}
.wn-rel{position:relative;padding:16px 0 12px 18px;border-left:2px solid var(--wn-line);margin-left:6px}
.wn-rel::before{content:"";position:absolute;left:-7px;top:21px;width:12px;height:12px;border-radius:50%;background:#fff;border:2px solid #94a3b8}
.wn-rel.is-new::before{border-color:var(--wn-brand);background:var(--wn-brand);box-shadow:0 0 0 4px #e0e7ff}
.wn-rel h3{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin:0 0 8px;font-size:.9375rem;font-weight:700;color:var(--wn-ink)}
.wn-rel time{font-size:.75rem;font-weight:500;color:var(--wn-muted)}
.wn-unseen{--bs-badge-font-size:.6875rem;--bs-badge-padding-x:7px;--bs-badge-color:#9f1239;background:#ffe4e6}
.wn-changes{display:grid;gap:8px}
.wn-tag{--bs-badge-font-size:.6875rem;--bs-badge-padding-y:3px;--bs-badge-border-radius:6px;flex:none;min-width:70px;margin-top:2px;letter-spacing:.02em}
.wn-tag.new{background:#dcfce7;color:#166534}.wn-tag.imp{background:#e0e7ff;color:#3730a3}.wn-tag.fix{background:#fef3c7;color:#92400e}
.wn-foot{padding:12px 18px 14px;border-top:1px solid var(--wn-line)}
.wn-link{color:#4338ca;font-weight:650;text-decoration:none}
.wn-link:hover{color:#4338ca;text-decoration:underline}
.wn .btn-outline-secondary,.wn-spot .btn{--bs-btn-padding-x:12px;--bs-btn-font-size:.8125rem;--bs-btn-font-weight:650;--bs-btn-border-radius:9px;--bs-btn-focus-box-shadow:0 0 0 2px #fff,0 0 0 4px var(--wn-brand);height:34px;display:inline-grid;place-items:center}
.wn .btn-outline-secondary{--bs-btn-color:var(--wn-ink);--bs-btn-border-color:#cbd5e1;--bs-btn-hover-color:var(--wn-ink);--bs-btn-hover-bg:#f1f5f9;--bs-btn-hover-border-color:#cbd5e1;--bs-btn-active-color:var(--wn-ink);--bs-btn-active-bg:#e2e8f0;
  --bs-btn-disabled-color:#64748b;--bs-btn-disabled-bg:#f8fafc;--bs-btn-disabled-border-color:#e2e8f0;--bs-btn-disabled-opacity:1;background:#fff}
.wn .btn-outline-secondary:disabled{background:#f8fafc}
.wn-tools{padding:18px 20px}
.wn-tool{--bs-btn-padding-x:13px;--bs-btn-font-size:.8125rem;--bs-btn-font-weight:600;--bs-btn-color:var(--wn-ink);--bs-btn-bg:#fff;--bs-btn-border-color:#cbd5e1;--bs-btn-hover-color:var(--wn-ink);--bs-btn-hover-bg:#f8fafc;--bs-btn-hover-border-color:#cbd5e1;
  --bs-btn-border-radius:10px;--bs-btn-focus-box-shadow:0 0 0 2px #fff,0 0 0 4px var(--wn-brand);position:relative;display:inline-flex;align-items:center;gap:7px;height:36px}
.wn-tool{white-space:nowrap}
.wn-tool svg{width:16px;height:16px}
.wn-tool.is-spot{border-color:var(--wn-brand);box-shadow:0 0 0 4px #e0e7ff}
.wn-tool.is-spot::after{content:"";position:absolute;inset:-4px;border-radius:13px;border:2px solid var(--wn-brand);animation:wn-pulse 1.8s ease-out 3}
.wn-anchor{position:relative;display:inline-flex}
.wn-spot{--bs-popover-max-width:300px;--bs-popover-bg:#1e1b4b;--bs-popover-border-color:#1e1b4b;--bs-popover-border-radius:14px;--bs-popover-body-padding-x:16px;--bs-popover-body-padding-y:16px;--bs-popover-body-color:#c7d2fe;--bs-popover-font-size:.875rem;
  position:absolute;z-index:10;top:calc(100% + 14px);left:0;width:300px;max-width:calc(100vw - 64px);box-shadow:0 24px 48px -20px rgba(30,27,75,.6);font-family:inherit;animation:wn-in .2s ease-out}
.wn-spot .popover-arrow{left:22px}
.wn-spot h3{display:flex;align-items:center;gap:8px;margin:0 0 4px;font-size:.9375rem;font-weight:700;color:#fff}
.wn-spot h3 .badge{--bs-badge-font-size:.6875rem;--bs-badge-font-weight:800;--bs-badge-color:#1e1b4b;background:#a5b4fc}
.wn-spot p{margin:0 0 12px}
.wn-spot small{font-size:.75rem;color:#a5b4fc}
.wn-spot .btn-outline-light{--bs-btn-color:#e0e7ff;--bs-btn-border-color:rgba(199,210,254,.35);--bs-btn-hover-color:#e0e7ff;--bs-btn-hover-bg:rgba(199,210,254,.12);--bs-btn-hover-border-color:rgba(199,210,254,.35);--bs-btn-focus-box-shadow:0 0 0 2px #a5b4fc}
.wn-spot .btn-light{--bs-btn-color:#1e1b4b;--bs-btn-focus-box-shadow:0 0 0 2px #a5b4fc}
.wn-link:focus-visible{outline:2px solid var(--wn-brand);outline-offset:2px}
@keyframes wn-in{from{opacity:0;transform:translateY(-6px) scale(.98)}}
@keyframes wn-pulse{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.25)}}
@media (max-width:520px){.wn-host{position:static}.wn-panel{left:0;right:0;width:auto;max-width:none}.wn-panel .popover-arrow{display:none}.wn-tag{min-width:62px}}
@media (prefers-reduced-motion:reduce){.wn-panel,.wn-spot{animation:none}.wn-tool.is-spot::after{animation:none;opacity:0}}
JavaScript
(function () {
  // "What's new" popover
  document.querySelectorAll('[data-wn]').forEach(function (wn) {
    var trigger = wn.querySelector('[data-wn-trigger]'), panel = wn.querySelector('[data-wn-panel]');
    if (!trigger || !panel) return;
    var seen = wn.querySelector('[data-wn-seen]'), status = wn.querySelector('[data-wn-status]');
    function setOpen(open) { trigger.setAttribute('aria-expanded', open); panel.hidden = !open; }
    trigger.addEventListener('click', function () { setOpen(panel.hidden); });
    wn.querySelector('[data-wn-close]').addEventListener('click', function () { setOpen(false); trigger.focus(); });
    seen.addEventListener('click', function () {
      wn.querySelectorAll('[data-wn-unseen], [data-wn-dot]').forEach(function (el) { el.hidden = true; });
      wn.querySelectorAll('[data-wn-new]').forEach(function (el) { el.removeAttribute('data-wn-new'); el.classList.remove('is-new'); });
      seen.disabled = true; seen.textContent = 'All seen';
      status.textContent = 'Updates marked as seen';
    });
    wn.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !panel.hidden) { setOpen(false); trigger.focus(); } });
    document.addEventListener('click', function (e) { if (!panel.hidden && !wn.contains(e.target)) setOpen(false); });
  });

  // Feature spotlight: "Got it" closes the tip, removes the highlight and returns focus to the feature.
  document.querySelectorAll('[data-wn-spot-host]').forEach(function (host) {
    var spot = host.querySelector('[data-wn-spot]'), target = host.querySelector('[data-wn-spot-target]');
    function close() { spot.hidden = true; target.classList.remove('is-spot'); target.removeAttribute('aria-describedby'); target.focus(); }
    host.querySelector('[data-wn-spot-close]').addEventListener('click', close);
    host.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !spot.hidden) close(); });
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-spark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/></symbol>
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="i-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></symbol>
  <symbol id="i-bookmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3.5h12v17l-6-4-6 4z"/></symbol>
  <symbol id="i-filter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5h18l-7 8.5V19l-4 2v-7.5z"/></symbol>
  <symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></symbol>
</svg>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">“What’s new” popover with versions and change types</span>
<div class="relative rounded-[18px] border border-[#d6dce5] bg-[#f8fafc] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-28px_rgba(15,23,42,.3)] h-[700px] max-[600px]:h-[820px]">
  <div class="relative flex h-16 items-center gap-3.5 rounded-t-[18px] border-b border-[#e2e8f0] bg-white px-5"><b class="mr-auto text-[#0f172a]">Ledgerly</b>
    <div class="relative text-sm leading-normal text-[#475569] max-[520px]:static" data-wn>
      <button class="relative inline-flex h-[38px] cursor-pointer items-center gap-2 rounded-full border border-[#c7d2fe] bg-[#eef2ff] pr-3.5 pl-3 text-sm font-[650] text-[#3730a3] hover:bg-[#e0e7ff] aria-expanded:bg-[#e0e7ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" aria-expanded="true" aria-controls="wn-panel" data-wn-trigger><svg class="size-[17px]" aria-hidden="true"><use href="#i-spark"/></svg>What’s new<span class="absolute -top-0.5 -right-0.5 size-[11px] rounded-full border-2 border-white bg-[#e11d48]" data-wn-dot><span class="sr-only">, 2 unseen updates</span></span></button>
      <section class="absolute top-[calc(100%+12px)] right-0 z-20 w-[420px] max-w-[calc(100vw-32px)] origin-top-right animate-pop-in rounded-[18px] border border-[#e2e8f0] bg-white shadow-[0_30px_60px_-24px_rgba(15,23,42,.38),0_4px_12px_rgba(15,23,42,.06)] before:absolute before:-top-[7px] before:right-11 before:size-3 before:rotate-45 before:border-t before:border-l before:border-[#e2e8f0] before:bg-white motion-reduce:animate-none max-[520px]:right-0 max-[520px]:left-0 max-[520px]:w-auto max-[520px]:max-w-none max-[520px]:before:hidden" id="wn-panel" aria-labelledby="wn-title" data-wn-panel>
        <div class="flex items-center gap-2.5 pt-4 pr-3.5 pb-3 pl-[18px]"><h2 class="m-0 flex-1 text-base font-bold text-[#0f172a]" id="wn-title">What’s new in Ledgerly</h2><button class="grid size-8 cursor-pointer place-items-center rounded-[9px] border-0 bg-transparent p-0 text-[#64748b] hover:bg-[#f1f5f9] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" aria-label="Close what’s new" data-wn-close><svg class="size-[15px]" aria-hidden="true"><use href="#i-x"/></svg></button></div>
        <div class="max-h-[440px] overflow-y-auto px-[18px] pb-1.5">
          <div class="relative mb-1.5 overflow-hidden rounded-[14px] bg-[radial-gradient(120%_140%_at_100%_0%,#7c3aed_0%,transparent_55%),linear-gradient(135deg,#312e81,#1e1b4b)] p-[18px] text-[#e0e7ff] after:absolute after:-right-7 after:-bottom-[34px] after:size-[130px] after:rotate-[18deg] after:rounded-[28px] after:bg-white/7">
            <span class="inline-block rounded-full bg-white/14 px-[9px] py-0.5 text-xs font-bold tracking-[.02em] text-white">Version 4.2</span>
            <h3 class="mt-2.5 mb-1 text-[1.0625rem] font-bold text-white">Bulk edit invoices</h3>
            <p class="relative z-1 mt-0 mb-3 max-w-[300px]">Select up to 200 invoices and change due dates, tax rates or status in one go.</p>
            <a class="relative z-1 inline-flex items-center gap-1.5 rounded-[9px] bg-white px-3 py-[7px] font-[650] text-[#312e81] no-underline hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Try bulk edit<svg class="size-3.5" aria-hidden="true"><use href="#i-arrow"/></svg></a>
          </div>
          <article class="relative ml-1.5 border-l-2 border-[#e2e8f0] pt-4 pb-3 pl-[18px] before:absolute before:top-[21px] before:-left-[7px] before:size-3 before:rounded-full before:border-2 before:border-[#94a3b8] before:bg-white [&.is-new]:before:border-[#4f46e5] [&.is-new]:before:bg-[#4f46e5] [&.is-new]:before:shadow-[0_0_0_4px_#e0e7ff] is-new" data-wn-new>
            <h3 class="m-0 mb-2 flex flex-wrap items-baseline gap-x-2.5 gap-y-1 text-[.9375rem] font-bold text-[#0f172a]">4.2 <time class="text-xs font-medium text-[#5b6474]" datetime="2026-10-14">14 Oct 2026</time><span class="rounded-full bg-[#ffe4e6] px-[7px] py-px text-[.6875rem] font-bold text-[#9f1239]" data-wn-unseen>Unseen</span></h3>
            <ul class="m-0 grid list-none gap-2 p-0">
              <li class="flex items-start gap-[9px]"><span class="mt-px min-w-[70px] flex-none rounded-md py-px text-center text-[.6875rem] font-bold tracking-[.02em] max-[520px]:min-w-[62px] bg-[#dcfce7] text-[#166534]">New</span>Bulk edit for up to 200 invoices at once.</li>
              <li class="flex items-start gap-[9px]"><span class="mt-px min-w-[70px] flex-none rounded-md py-px text-center text-[.6875rem] font-bold tracking-[.02em] max-[520px]:min-w-[62px] bg-[#e0e7ff] text-[#3730a3]">Improved</span>Search now finds invoices by amount, like “1,240”.</li>
            </ul>
          </article>
          <article class="relative ml-1.5 border-l-2 border-[#e2e8f0] pt-4 pb-3 pl-[18px] before:absolute before:top-[21px] before:-left-[7px] before:size-3 before:rounded-full before:border-2 before:border-[#94a3b8] before:bg-white [&.is-new]:before:border-[#4f46e5] [&.is-new]:before:bg-[#4f46e5] [&.is-new]:before:shadow-[0_0_0_4px_#e0e7ff] is-new" data-wn-new>
            <h3 class="m-0 mb-2 flex flex-wrap items-baseline gap-x-2.5 gap-y-1 text-[.9375rem] font-bold text-[#0f172a]">4.1 <time class="text-xs font-medium text-[#5b6474]" datetime="2026-09-30">30 Sep 2026</time><span class="rounded-full bg-[#ffe4e6] px-[7px] py-px text-[.6875rem] font-bold text-[#9f1239]" data-wn-unseen>Unseen</span></h3>
            <ul class="m-0 grid list-none gap-2 p-0">
              <li class="flex items-start gap-[9px]"><span class="mt-px min-w-[70px] flex-none rounded-md py-px text-center text-[.6875rem] font-bold tracking-[.02em] max-[520px]:min-w-[62px] bg-[#e0e7ff] text-[#3730a3]">Improved</span>The dashboard loads about 40% faster on large accounts.</li>
              <li class="flex items-start gap-[9px]"><span class="mt-px min-w-[70px] flex-none rounded-md py-px text-center text-[.6875rem] font-bold tracking-[.02em] max-[520px]:min-w-[62px] bg-[#fef3c7] text-[#92400e]">Fixed</span>PDF exports keep your logo on every page.</li>
            </ul>
          </article>
          <article class="relative ml-1.5 border-l-2 border-[#e2e8f0] pt-4 pb-3 pl-[18px] before:absolute before:top-[21px] before:-left-[7px] before:size-3 before:rounded-full before:border-2 before:border-[#94a3b8] before:bg-white [&.is-new]:before:border-[#4f46e5] [&.is-new]:before:bg-[#4f46e5] [&.is-new]:before:shadow-[0_0_0_4px_#e0e7ff]">
            <h3 class="m-0 mb-2 flex flex-wrap items-baseline gap-x-2.5 gap-y-1 text-[.9375rem] font-bold text-[#0f172a]">4.0 <time class="text-xs font-medium text-[#5b6474]" datetime="2026-09-02">2 Sep 2026</time></h3>
            <ul class="m-0 grid list-none gap-2 p-0">
              <li class="flex items-start gap-[9px]"><span class="mt-px min-w-[70px] flex-none rounded-md py-px text-center text-[.6875rem] font-bold tracking-[.02em] max-[520px]:min-w-[62px] bg-[#dcfce7] text-[#166534]">New</span>Reports in more than one currency, converted daily.</li>
            </ul>
          </article>
        </div>
        <div class="flex flex-wrap items-center justify-between gap-2 border-t border-[#e2e8f0] px-[18px] pt-3 pb-3.5"><a class="font-[650] text-[#4338ca] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#">Read the full changelog</a><button class="inline-grid h-[34px] cursor-pointer place-items-center rounded-[9px] border px-3 text-[.8125rem] font-[650] no-underline border-[#cbd5e1] bg-white text-[#0f172a] hover:bg-[#f1f5f9] disabled:cursor-default disabled:border-[#e2e8f0] disabled:bg-[#f8fafc] disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" data-wn-seen>Mark as seen</button></div>
        <p class="sr-only" role="status" data-wn-status></p>
      </section>
    </div>
  </div>
  <div class="grid gap-3 p-6" aria-hidden="true"><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i></div>
</div></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Feature spotlight pointing at a new button</span>
<div class="relative rounded-[18px] border border-[#d6dce5] bg-[#f8fafc] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-28px_rgba(15,23,42,.3)] h-[340px] max-[600px]:h-[440px]">
  <div class="relative flex h-16 items-center gap-3.5 rounded-t-[18px] border-b border-[#e2e8f0] bg-white px-5"><b class="mr-auto text-[#0f172a]">Invoices</b></div>
  <div class="relative text-sm leading-normal text-[#475569]" data-wn-spot-host>
    <div class="flex flex-wrap gap-2 px-5 py-[18px]">
      <div class="relative inline-flex"><button class="relative inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold whitespace-nowrap text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] [&.is-spot]:border-[#4f46e5] [&.is-spot]:shadow-[0_0_0_4px_#e0e7ff] [&.is-spot]:after:absolute [&.is-spot]:after:-inset-1 [&.is-spot]:after:animate-spot-pulse [&.is-spot]:after:rounded-[13px] [&.is-spot]:after:border-2 [&.is-spot]:after:border-[#4f46e5] motion-reduce:[&.is-spot]:after:animate-none motion-reduce:[&.is-spot]:after:opacity-0 is-spot" type="button" id="wn-views" aria-describedby="wn-spot-text" data-wn-spot-target><svg class="size-4" aria-hidden="true"><use href="#i-bookmark"/></svg>Saved views</button>
      <section class="absolute top-[calc(100%+14px)] left-0 z-10 w-[300px] max-w-[calc(100vw-64px)] animate-pop-in rounded-[14px] bg-[#1e1b4b] px-4 pt-4 pb-3.5 text-[#c7d2fe] shadow-[0_24px_48px_-20px_rgba(30,27,75,.6)] before:absolute before:-top-1.5 before:left-7 before:size-3 before:rotate-45 before:bg-[#1e1b4b] motion-reduce:animate-none" aria-labelledby="wn-spot-title" data-wn-spot>
        <h3 class="m-0 mb-1 flex items-center gap-2 text-[.9375rem] font-bold text-white" id="wn-spot-title"><span class="rounded-full bg-[#a5b4fc] px-[7px] py-px text-[.6875rem] font-extrabold text-[#1e1b4b]">New</span>Saved views</h3>
        <p class="mt-0 mb-3" id="wn-spot-text">Save any filter and sort as a view, then share it with your team.</p>
        <div class="flex items-center gap-2"><small class="mr-auto text-xs text-[#a5b4fc]">Tip 1 of 1</small><a class="inline-grid h-[34px] cursor-pointer place-items-center rounded-[9px] border px-3 text-[.8125rem] font-[650] no-underline border-[#c7d2fe]/35 bg-transparent text-[#e0e7ff] hover:bg-[#c7d2fe]/12 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Learn more</a><button class="inline-grid h-[34px] cursor-pointer place-items-center rounded-[9px] border px-3 text-[.8125rem] font-[650] no-underline border-white bg-white text-[#1e1b4b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" type="button" data-wn-spot-close>Got it</button></div>
      </section>
      </div>
      <button class="relative inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold whitespace-nowrap text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button"><svg class="size-4" aria-hidden="true"><use href="#i-filter"/></svg>Filter</button>
      <button class="relative inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold whitespace-nowrap text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button"><svg class="size-4" aria-hidden="true"><use href="#i-download"/></svg>Export</button>
    </div>
  </div>
  <div class="grid gap-3 p-6" aria-hidden="true"><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i></div>
</div></div>
CSS
@theme {
  --animate-pop-in: pop-in .2s ease-out;
  --animate-spot-pulse: spot-pulse 1.8s ease-out 3;
  @keyframes pop-in { from { opacity: 0; transform: translateY(-6px) scale(.98); } }
  @keyframes spot-pulse { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(1.25); } }
}
JavaScript
(function () {
  // "What's new" popover
  document.querySelectorAll('[data-wn]').forEach(function (wn) {
    var trigger = wn.querySelector('[data-wn-trigger]'), panel = wn.querySelector('[data-wn-panel]');
    if (!trigger || !panel) return;
    var seen = wn.querySelector('[data-wn-seen]'), status = wn.querySelector('[data-wn-status]');
    function setOpen(open) { trigger.setAttribute('aria-expanded', open); panel.hidden = !open; }
    trigger.addEventListener('click', function () { setOpen(panel.hidden); });
    wn.querySelector('[data-wn-close]').addEventListener('click', function () { setOpen(false); trigger.focus(); });
    seen.addEventListener('click', function () {
      wn.querySelectorAll('[data-wn-unseen], [data-wn-dot]').forEach(function (el) { el.hidden = true; });
      wn.querySelectorAll('[data-wn-new]').forEach(function (el) { el.removeAttribute('data-wn-new'); el.classList.remove('is-new'); });
      seen.disabled = true; seen.textContent = 'All seen';
      status.textContent = 'Updates marked as seen';
    });
    wn.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !panel.hidden) { setOpen(false); trigger.focus(); } });
    document.addEventListener('click', function (e) { if (!panel.hidden && !wn.contains(e.target)) setOpen(false); });
  });

  // Feature spotlight: "Got it" closes the tip, removes the highlight and returns focus to the feature.
  document.querySelectorAll('[data-wn-spot-host]').forEach(function (host) {
    var spot = host.querySelector('[data-wn-spot]'), target = host.querySelector('[data-wn-spot-target]');
    function close() { spot.hidden = true; target.classList.remove('is-spot'); target.removeAttribute('aria-describedby'); target.focus(); }
    host.querySelector('[data-wn-spot-close]').addEventListener('click', close);
    host.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !spot.hidden) close(); });
  });
})();

About this set

In-app announcements for product updates. A What’s new pill in the app header carries a red dot and opens a popover panel with a featured release card for version 4.2, drawn with CSS gradients and a Try bulk edit link, followed by a release timeline for 4.2, 4.1 and 4.0. Each release is an article with the version as a heading, a date in a time element and an Unseen badge when it is new, and each change starts with a text tag: New, Improved or Fixed, so the type never depends on colour. The footer links to the full changelog and has a Mark as seen button that clears the dot and the badges. The second variant is a feature spotlight: a new Saved views button gets a highlight ring that pulses three times, and a dark callout with an arrow explains the feature with Learn more and Got it. Use the panel for a changelog and the spotlight for one new feature after a release. The Bootstrap version uses static .popover.bs-popover-bottom markup with .popover-arrow, .popover-header and .popover-body, so Bootstrap’s JavaScript is not needed.

What’s included

  • Featured release card plus a version timeline
  • New, Improved and Fixed tags written as text
  • Unseen dot and badges cleared by Mark as seen
  • Feature spotlight callout with a three-pulse highlight
  • Bootstrap static .popover markup with no JavaScript bundle

Accessibility

The trigger has aria-expanded and aria-controls, and its name includes the unseen count through visually hidden text. The panel is a region named by its heading, releases are articles with headings, and Mark as seen is announced through a polite status region. Esc or the close button returns focus to the trigger. The spotlit button is described by the callout text with aria-describedby, Got it returns focus to it, every control has a focus outline, and the panel animation and pulse are off under prefers-reduced-motion.

Customise it

In plain CSS change --wn-brand, --wn-ink, --wn-text and --wn-line on .wn, the gradient on .wn-hero and the tag colours on .wn-tag.new, .imp and .fix. In Bootstrap the panel takes the --bs-popover-* variables; in Tailwind edit the gradient and hex values in the utilities.