Component

Persistent Toast

Toast that stays open until explicitly dismissed, with a shrinking progress bar showing the remaining time. The bar pauses on hover and resumes on leave. Ideal for upload results and important notices that shouldn't be missed.

  • Component
  • Tailwind
  • HTML
  • MIT

What it does

Capabilities

  • Countdown progress bar
  • Stays open until dismissed
  • Pause on hover
  • Resume on leave
  • Close button
  • Role status
  • Aria-live polite

Preview

Live component

Interactive preview
Open preview
9:41 100%
devsnips.site/library/tailwind/components/toasts/persistent-toast/
fluid · no fixed width 100%
No dependencies Focus-visible

Install

Add to your project

terminal
npx devsnips add Tailwind/Components/Toasts/persistent-toast
registry path: Tailwind/Components/Toasts/persistent-toast

Source

Implementation

code.html
<!--
Snippet Name: Persistent Toast
Description: Toast that stays open until explicitly dismissed, with a shrinking progress bar showing the remaining time. Great for upload/download progress or important notices.
Author: DevSnips Contributors
Usage Example: Call showToast() for a non-ignorable confirmation that still times out.
-->
<div
  id="persistent-toast-region"
  class="pointer-events-none fixed bottom-4 right-4 z-50 flex w-full max-w-sm flex-col items-end gap-3"
  role="region"
  aria-label="Notifications"
  aria-live="polite"
  data-toast-region
></div>

<!-- Demo trigger button -->
<button onclick="showToast()" class="inline-flex items-center gap-2 rounded-xl bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2">
  <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3 3-3"/></svg>
  Upload file
</button>

<script>
  (function () {
    const root = document.currentScript.closest('[data-toast-region]');
    if (!root) return;
    const DURATION = 6000;

    function dismiss(toast) {
      if (!toast || toast.dataset.dismissed === 'true') return;
      toast.dataset.dismissed = 'true';
      toast.classList.add('translate-x-2', 'opacity-0');
      toast.addEventListener('transitionend', () => toast.remove(), { once: true });
      setTimeout(() => toast.remove(), 400);
    }

    function showToast() {
      const toast = document.createElement('div');
      toast.className = 'toast-item pointer-events-auto relative w-full overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg ring-1 ring-black/5 transition duration-300 ease-out';
      toast.setAttribute('role', 'status');
      toast.innerHTML = `
        <div class="flex items-start gap-3 p-4">
          <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-100 text-blue-600">
            <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3 3-3"/></svg>
          </span>
          <div class="min-w-0 flex-1">
            <p class="text-sm font-semibold text-gray-900">Upload complete</p>
            <p class="mt-0.5 text-sm text-gray-500">design-system.fig was uploaded successfully.</p>
          </div>
          <button type="button" class="toast-close shrink-0 rounded-md p-1 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400" aria-label="Dismiss notification">
            <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
          </button>
        </div>
        <div class="toast-progress h-1 w-full origin-left scale-x-100 bg-blue-500 transition-transform ease-linear" style="transform: scaleX(1)"></div>`;

      root.appendChild(toast);

      const bar = toast.querySelector('.toast-progress');
      const closeBtn = toast.querySelector('.toast-close');

      // Start the countdown bar on the next frame so the transition runs
      function startBar() {
        requestAnimationFrame(() => {
          bar.style.transitionDuration = DURATION + 'ms';
          bar.style.transform = 'scaleX(0)';
        });
      }
      startBar();

      const timer = setTimeout(() => dismiss(toast), DURATION);
      function pause() { clearTimeout(timer); bar.style.transitionDuration = '0ms'; }
      function resume() {
        const rect = bar.getBoundingClientRect();
        const remaining = rect.width > 0 ? DURATION * (rect.width / bar.offsetWidth) : DURATION;
        bar.style.transitionDuration = remaining + 'ms';
        bar.style.transform = 'scaleX(0)';
      }

      closeBtn.addEventListener('click', () => { clearTimeout(timer); dismiss(toast); });
      toast.addEventListener('mouseenter', () => pause());
      toast.addEventListener('mouseleave', () => resume());
    }

    window.showToast = showToast;
  })();
</script>
85 lines UTF-8 · LF · Spaces: 2
DevSnips AI ready Claude · Cursor · Windsurf

Agent skill & Model Context Protocol

Pass this component straight into your AI workflow. DevSnips exposes each resource as a structured, addressable registry entry, so your agent installs the real files instead of approximating them.

devsnips://components/toasts/persistent-toast
Prompt instruction
I want to use this DevSnips resource in production:

Page:
https://devsnips.site/library/tailwind/components/toasts/persistent-toast/

Install: npx devsnips add
Tailwind/Components/Toasts/persistent-toast

This is the Persistent Toast — Toast that stays open until explicitly dismissed, with a shrinking progress bar showing the remaining time. The bar pauses on hover and resumes on leave. Ideal for upload results and important notices that shouldn't be missed.

Use this DevSnips resource as the starting point for my project.
Adapt it to my project's stack and conventions, fix any issues,
and make it production-ready while preserving its intended design
and behavior.

Paste into ChatGPT, Claude or your agent — it already carries the page URL and the exact install path.

Resource URI
devsnips://components/toasts/persistent-toast