Component

Glass Toast

Frosted-glass notification toasts in the glassmorphism design language — translucent backdrop-blur panels with semantic glowing accents, a live countdown progress bar, and pause-on-hover, presented over an animated aurora mesh backdrop.

  • Component
  • Tailwind
  • HTML
  • MIT

Preview

Live component

Interactive preview
Open preview
9:41 100%
devsnips.dev/library/tailwind/components/toasts/glass-toast/
fluid · no fixed width 100%
No dependencies Responsive · Focus-visible · Reduced-motion

Install

Add to your project

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

Source

Implementation

code.html
<!--
Snippet Name: Glass Toast
Description: Frosted-glass notification toasts with semantic glowing accents, a countdown progress bar, and pause-on-hover, presented over an animated aurora mesh backdrop.
Author: DevSnips Contributors
Usage Example: Call showGlassToast('success'|'error'|'warning'|'info') after async results; the demo panel can be dropped when wiring into a real app.
-->
<div data-glass-toast="glass-toast">
  <!-- Scoped styles: aurora drift animation + reduced-motion guard (pure CSS, no config needed) -->
  <style>
    @keyframes gt-drift-a {
      0%, 100% { transform: translate(0, 0) scale(1); }
      50% { transform: translate(28px, 18px) scale(1.1); }
    }
    @keyframes gt-drift-b {
      0%, 100% { transform: translate(0, 0) scale(1); }
      50% { transform: translate(-24px, -14px) scale(1.06); }
    }
    [data-glass-toast] .gt-blob-a { animation: gt-drift-a 14s ease-in-out infinite; }
    [data-glass-toast] .gt-blob-b { animation: gt-drift-b 18s ease-in-out infinite; }
    @media (prefers-reduced-motion: reduce) {
      [data-glass-toast] *,
      [data-glass-toast] *::before,
      [data-glass-toast] *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
      }
    }
  </style>

  <!-- Toast region (fixed viewport corner) -->
  <div
    class="gt-region pointer-events-none fixed bottom-4 right-4 z-50 flex w-full max-w-sm flex-col items-end gap-3 px-4 sm:px-0"
    role="region"
    aria-label="Notifications"
    aria-live="polite"
  ></div>

  <!-- Demo panel: aurora mesh backdrop + trigger buttons -->
  <div class="relative overflow-hidden rounded-3xl bg-[#0B1026] p-6 ring-1 ring-white/10 sm:p-8">
    <div aria-hidden="true" class="pointer-events-none absolute inset-0">
      <div class="gt-blob-a absolute -left-24 -top-24 h-72 w-72 rounded-full bg-fuchsia-500/40 blur-3xl"></div>
      <div class="gt-blob-b absolute -right-20 top-1/3 h-80 w-80 rounded-full bg-indigo-500/40 blur-3xl"></div>
      <div class="gt-blob-a absolute -bottom-28 left-1/4 h-72 w-72 rounded-full bg-cyan-400/30 blur-3xl"></div>
    </div>

    <div class="relative">
      <p class="text-xs font-semibold uppercase tracking-widest text-cyan-300">Glassmorphism</p>
      <h3 class="mt-2 text-xl font-bold tracking-tight text-white">Deployment notifications</h3>
      <p class="mt-2 max-w-md text-sm leading-relaxed text-slate-300">
        Frosted-glass toasts with a live countdown. Fire one below — hovering a toast pauses its timer.
      </p>

      <div class="mt-6 flex flex-wrap gap-3">
        <button type="button" data-gt-trigger="success" class="inline-flex items-center gap-2 rounded-xl bg-gradient-to-r from-fuchsia-500 to-indigo-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-fuchsia-500/25 transition-colors duration-200 hover:from-fuchsia-400 hover:to-indigo-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
          <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="M5 13l4 4L19 7"/></svg>
          Deployment live
        </button>
        <button type="button" data-gt-trigger="error" class="inline-flex items-center gap-2 rounded-xl border border-white/15 bg-white/10 px-4 py-2.5 text-sm font-semibold text-white backdrop-blur transition-colors duration-200 hover:bg-white/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
          <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>
          Build failed
        </button>
        <button type="button" data-gt-trigger="warning" class="inline-flex items-center gap-2 rounded-xl border border-white/15 bg-white/10 px-4 py-2.5 text-sm font-semibold text-white backdrop-blur transition-colors duration-200 hover:bg-white/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
          <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="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>
          Quota warning
        </button>
        <button type="button" data-gt-trigger="info" class="inline-flex items-center gap-2 rounded-xl border border-white/15 bg-white/10 px-4 py-2.5 text-sm font-semibold text-white backdrop-blur transition-colors duration-200 hover:bg-white/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
          <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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
          New comment
        </button>
      </div>
    </div>
  </div>

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

      const variants = {
        success: {
          icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>',
          chip: 'bg-emerald-400/15 text-emerald-300 ring-emerald-300/30',
          bar: 'bg-emerald-400',
          glow: 'shadow-[0_0_36px_-10px_rgba(52,211,153,0.55)]',
        },
        error: {
          icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>',
          chip: 'bg-rose-400/15 text-rose-300 ring-rose-300/30',
          bar: 'bg-rose-400',
          glow: 'shadow-[0_0_36px_-10px_rgba(251,113,133,0.55)]',
        },
        warning: {
          icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>',
          chip: 'bg-amber-400/15 text-amber-300 ring-amber-300/30',
          bar: 'bg-amber-400',
          glow: 'shadow-[0_0_36px_-10px_rgba(251,191,36,0.55)]',
        },
        info: {
          icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>',
          chip: 'bg-cyan-400/15 text-cyan-300 ring-cyan-300/30',
          bar: 'bg-cyan-400',
          glow: 'shadow-[0_0_36px_-10px_rgba(103,232,249,0.55)]',
        },
      };

      const copy = {
        success: { title: 'Deployment live', body: 'acme-web was deployed to production in 42s.' },
        error: { title: 'Build failed', body: 'Type error in src/api/billing.ts on line 118.' },
        warning: { title: 'Bandwidth at 80%', body: 'Your quota resets in 6 days. Upgrade to avoid throttling.' },
        info: { title: 'New comment', body: 'Priya mentioned you in #design-system.' },
      };

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

      function showGlassToast(type) {
        const v = variants[type] || variants.info;
        const c = copy[type] || copy.info;
        const toast = document.createElement('div');
        toast.className = `pointer-events-auto relative w-full translate-y-3 overflow-hidden rounded-2xl border border-white/15 bg-slate-950/60 opacity-0 shadow-[0_8px_32px_rgba(2,6,23,0.45)] ring-1 ring-white/10 backdrop-blur-2xl transition duration-300 ease-out ${v.glow}`;
        toast.setAttribute('role', type === 'error' || type === 'warning' ? 'alert' : 'status');
        toast.innerHTML = `
          <div class="flex items-start gap-3 p-4">
            <span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full ring-1 ${v.chip}">
              <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">${v.icon}</svg>
            </span>
            <div class="min-w-0 flex-1">
              <p class="text-sm font-semibold text-white">${c.title}</p>
              <p class="mt-0.5 text-sm leading-relaxed text-slate-200">${c.body}</p>
            </div>
            <button type="button" class="gt-close shrink-0 rounded-lg p-1.5 text-slate-400 transition-colors duration-200 hover:bg-white/10 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300" 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="h-0.5 w-full bg-white/10">
            <div class="gt-bar h-full w-full origin-left ${v.bar}" style="transform: scaleX(1); transition-property: transform; transition-timing-function: linear;"></div>
          </div>`;
        region.appendChild(toast);

        // Enter on the next frame so the transition runs
        requestAnimationFrame(() => {
          requestAnimationFrame(() => toast.classList.remove('translate-y-3', 'opacity-0'));
        });

        const bar = toast.querySelector('.gt-bar');
        let remaining = DURATION;
        let startedAt = Date.now();
        let timer = setTimeout(() => dismiss(toast), remaining);

        function runBar(ms) {
          bar.style.transitionDuration = ms + 'ms';
          bar.style.transform = 'scaleX(0)';
        }
        requestAnimationFrame(() => runBar(remaining));

        function pause() {
          clearTimeout(timer);
          remaining = Math.max(0, remaining - (Date.now() - startedAt));
          const ratio = bar.offsetWidth > 0 ? bar.getBoundingClientRect().width / bar.offsetWidth : 0;
          bar.style.transitionDuration = '0ms';
          bar.style.transform = 'scaleX(' + ratio.toFixed(4) + ')';
        }
        function resume() {
          startedAt = Date.now();
          timer = setTimeout(() => dismiss(toast), remaining);
          runBar(remaining);
        }

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

      root.querySelectorAll('[data-gt-trigger]').forEach((btn) => {
        btn.addEventListener('click', () => showGlassToast(btn.dataset.gtTrigger));
      });

      window.showGlassToast = showGlassToast;
    })();
  </script>
</div>
189 lines UTF-8 · LF · Spaces: 2

AI-ready

Available to your agent.

This component is reachable through the DevSnips CLI, MCP server and Skills integrations — one registry, one resource path.

Resource path

devsnips://components/toasts/glass-toast

Continue browsing

View all