CSS + Datastar toasts

HTML

<script
  type="module"
  defer
  src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"
></script>

<p>
  Signal free CSS + Datastar toasts with in and out animations, progress bar,
  and pause-on-hover. Add icons, close buttons, and style as needed.
</p>
<div class="toast-container"></div>
<button
  data-on:click="
                const container = document.querySelector('.toast-container');
                const t = document.querySelector('#toast-template');
                const n = t.content.firstElementChild.cloneNode(true);
                container.appendChild(n);
            "
>
  Show toast
</button>
<template id="toast-template">
  <div
    class="toast toast-entering"
    style="--toast-duration: 5000ms"
    data-on:animationend="event.animationName?.startsWith('toast-in') && el.classList.remove('toast-entering');event.animationName?.startsWith('toast-out') && el.remove()"
  >
    <div class="toast-body">Toast: <span data-text="Math.random()"></span></div>
    <div
      class="toast-progress"
      data-on:animationend="el.closest('.toast')?.classList.add('toast-leaving')"
    ></div>
  </div>
</template>

CSS

.toast {
  display: flex;
  flex-direction: column;
  border: solid 1px black;
  width: 300px;
  padding: 0.5rem;
  cursor: default;
  background-color: whitesmoke;
  animation-play-state: running;
}

.toast-progress {
  background-color: blue;
  height: 4px;
}

.toast-entering {
  animation: toast-in 280ms ease-out both;
}

.toast-leaving {
  animation: toast-out 160ms ease-in both;
}

.toast-progress {
  width: 100%;
  transform-origin: left center;
  animation: toast-progress var(--toast-duration) linear both;
}

@keyframes toast-progress {
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
}

@keyframes toast-in {
  from {
    opacity: 0.25;
    transform: translateY(-30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes toast-out {
  from {
    opacity: 1;
    transform: translateY(0);
  }

  to {
    opacity: 0.25;
    transform: translateY(-30px);
  }
}
.toast-container {
  position: fixed;
  top: 2rem;
  right: 2rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.toast-container:hover .toast,
.toast-container:hover .toast-progress {
  animation-play-state: paused;
}

.toast-container:hover .toast.toast-entering,
.toast-container:hover .toast.toast-leaving,
.toast-container:hover .toast.toast-entering .toast-progress,
.toast-container:hover .toast.toast-leaving .toast-progress {
  animation-play-state: running;
}