<!-- Toasts acts like a container here without a backdrop -->
<button class="button ripple">Make Toast 🍞</button>
<div popover="manual" class="toasts">
  <ul class="toasts__drawer"></ul>
</div>
@layer normalize, open-props, base, mdl, demo;

@layer demo {
  .toasts {
    /* You can't use these as you need to use top and left...*/
    /*   bottom: 0;
    right: 0; */
    left: 100%;
    top: 100%;
    transform: translate(-100%, -100%);
    padding: var(--size-4);
    border: 0;
    overflow: auto;
    margin: 0;
    max-height: 100vh;
    background: 0;
    overflow-x: hidden;
  }

  .toasts__slice {
    list-style-type: none;
    margin: 0;
    animation: enter 250ms;
    transition: transform 0.2s;
    padding: 0;
  }

  .toasts__slice-control {
    background: var(--surface-1);
    white-space: nowrap;
    box-shadow: var(--shadow-3);
    border: 0;
    color: var(--gray-9);
    border-left: var(--size-2) solid var(--border, var(--green-3));
    border-radius: var(--radius-2);
    text-align: left;
    background: var(--background, var(--green-0));
    display: flex;
    align-items: center;
    gap: 1ch;
    max-width: 75vw;
    min-width: 285px;
    position: relative;
    padding: var(--size-4);
    outline-color: var(--border);
    height: auto;
  }

  @keyframes enter {
    0% {
      transform: scale(0);
    }
  }

  .toasts__slice:is(:hover, :focus) {
    transform: translateY(-5%);
  }

  .toasts__slice:active {
    transform: translateY(5%);
  }

  .toasts__slice--success {
    --background: var(--green-0);
    --border: var(--green-3);
  }
  .toasts__slice--destructive {
    --background: var(--red-0);
    --border: var(--red-3);
  }
  .toasts__slice--warning {
    --background: var(--yellow-0);
    --border: var(--yellow-3);
  }
  .toasts__slice--info {
    --background: var(--blue-0);
    --border: var(--blue-3);
  }
  .toasts__slice--timed {
    --background: var(--indigo-0);
    --border: var(--indigo-3);
  }

  .toasts__slice svg {
    fill: var(--border);
    stroke: 0;
    width: 3ch;
    min-width: 3ch;
    aspect-ratio: 1;
  }

  .toast__close {
    width: 1ch;
    aspect-ratio: 1;
    min-width: 1ch;
  }

  .toast__text {
    white-space: normal;
    flex: 1;
    text-align: center;
  }

  .toasts__drawer {
    display: grid;
    gap: var(--size-4);
    padding: 0;
  }
}

@layer base {
  *,
  *:after,
  *:before {
    box-sizing: border-box;
  }

  :where([popover]) {
    margin: auto;
    border-width: initial;
    border-style: solid;
  }

  body {
    display: grid;
    place-items: center;
    min-height: 100vh;
    font-family: "Google Sans", sans-serif, system-ui;
    background: var(--surface-2);
  }
}
const TOASTER = document.querySelector("button");

const TOASTS = document.querySelector(".toasts");
const DRAWER = TOASTS.querySelector(".toasts__drawer");

const removeSlice = (slice, timeoutId) => () => {
  const ANIMATION = slice.animate(
    {
      transform: "translateX(200%) scale(0)"
    },
    {
      duration: 250,
      easing: "ease-in"
    }
  );
  ANIMATION.finished.then(() => {
    slice.remove();
    if (timeoutId) clearTimeout(timeoutId);
    // I mean, is there a penalty in having the :top-layer always open
    // if it doesn't obstruct?
    if (DRAWER.children.length === 0) TOASTS.hidePopover();
  });
};

const TOAST_TYPES = {
  SUCCESS: "success",
  INFO: "info",
  TIMED: "timed",
  WARNING: "warning",
  DESTRUCTIVE: "destructive"
};

const TOAST_OBJECTS = [
  {
    type: TOAST_TYPES.SUCCESS,
    text: "You made this happen! 🎉",
    icon: `<svg viewBox="0 0 512 512" title="check-double">
  <path d="M505 174.8l-39.6-39.6c-9.4-9.4-24.6-9.4-33.9 0L192 374.7 80.6 263.2c-9.4-9.4-24.6-9.4-33.9 0L7 302.9c-9.4 9.4-9.4 24.6 0 34L175 505c9.4 9.4 24.6 9.4 33.9 0l296-296.2c9.4-9.5 9.4-24.7.1-34zm-324.3 106c6.2 6.3 16.4 6.3 22.6 0l208-208.2c6.2-6.3 6.2-16.4 0-22.6L366.1 4.7c-6.2-6.3-16.4-6.3-22.6 0L192 156.2l-55.4-55.5c-6.2-6.3-16.4-6.3-22.6 0L68.7 146c-6.2 6.3-6.2 16.4 0 22.6l112 112.2z" />
</svg>`
  },
  {
    type: TOAST_TYPES.INFO,
    text: () => `It's currently ${new Date().toTimeString().slice(0, 8)} 🕰`,
    icon: `<svg viewBox="0 0 192 512" title="info">
  <path d="M20 424.229h20V279.771H20c-11.046 0-20-8.954-20-20V212c0-11.046 8.954-20 20-20h112c11.046 0 20 8.954 20 20v212.229h20c11.046 0 20 8.954 20 20V492c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20v-47.771c0-11.046 8.954-20 20-20zM96 0C56.235 0 24 32.235 24 72s32.235 72 72 72 72-32.235 72-72S135.764 0 96 0z" />
</svg>`
  },
  {
    type: TOAST_TYPES.WARNING,
    text: "You haven't finished yet 👀",
    icon: `<svg viewBox="0 0 576 512" title="exclamation-triangle">
  <path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z" />
</svg>`
  },
  {
    type: TOAST_TYPES.DESTRUCTIVE,
    text: "Something went wrong 😱",
    icon: `<svg viewBox="0 0 512 512" title="thumbs-down">
  <path d="M0 56v240c0 13.255 10.745 24 24 24h80c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24H24C10.745 32 0 42.745 0 56zm40 200c0-13.255 10.745-24 24-24s24 10.745 24 24-10.745 24-24 24-24-10.745-24-24zm272 256c-20.183 0-29.485-39.293-33.931-57.795-5.206-21.666-10.589-44.07-25.393-58.902-32.469-32.524-49.503-73.967-89.117-113.111a11.98 11.98 0 0 1-3.558-8.521V59.901c0-6.541 5.243-11.878 11.783-11.998 15.831-.29 36.694-9.079 52.651-16.178C256.189 17.598 295.709.017 343.995 0h2.844c42.777 0 93.363.413 113.774 29.737 8.392 12.057 10.446 27.034 6.148 44.632 16.312 17.053 25.063 48.863 16.382 74.757 17.544 23.432 19.143 56.132 9.308 79.469l.11.11c11.893 11.949 19.523 31.259 19.439 49.197-.156 30.352-26.157 58.098-59.553 58.098H350.723C358.03 364.34 384 388.132 384 430.548 384 504 336 512 312 512z" />
</svg>`
  },
  {
    type: TOAST_TYPES.TIMED,
    text: "Timed toast ⏱",
    icon: `<svg viewBox="0 0 512 512" title="history">
  <path d="M504 255.531c.253 136.64-111.18 248.372-247.82 248.468-59.015.042-113.223-20.53-155.822-54.911-11.077-8.94-11.905-25.541-1.839-35.607l11.267-11.267c8.609-8.609 22.353-9.551 31.891-1.984C173.062 425.135 212.781 440 256 440c101.705 0 184-82.311 184-184 0-101.705-82.311-184-184-184-48.814 0-93.149 18.969-126.068 49.932l50.754 50.754c10.08 10.08 2.941 27.314-11.313 27.314H24c-8.837 0-16-7.163-16-16V38.627c0-14.254 17.234-21.393 27.314-11.314l49.372 49.372C129.209 34.136 189.552 8 256 8c136.81 0 247.747 110.78 248 247.531zm-180.912 78.784l9.823-12.63c8.138-10.463 6.253-25.542-4.21-33.679L288 256.349V152c0-13.255-10.745-24-24-24h-16c-13.255 0-24 10.745-24 24v135.651l65.409 50.874c10.463 8.137 25.541 6.253 33.679-4.21z" />
</svg>`
  }
];

const makeToast = () => {
  // Create a toast...
  const { icon, text, type } = TOAST_OBJECTS[
    Math.floor(Math.random() * TOAST_OBJECTS.length)
  ];

  const slice = document.createElement("li");
  const sliceControl = document.createElement("button");
  sliceControl.className = "toasts__slice-control";
  sliceControl.title = "Remove notification";
  slice.appendChild(sliceControl);
  slice.classList.add("toasts__slice");
  slice.classList.add(`toasts__slice--${type}`);
  sliceControl.innerHTML = icon;
  sliceControl.innerHTML += `<span class="toast__text">${
    typeof text === "function" ? text() : text
  }</span>`;
  sliceControl.innerHTML += `<svg class="toast__close" viewBox="0 0 352 512" title="times">
  <path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
</svg>`;
  // Add it to the drawer
  DRAWER.appendChild(slice);
  TOASTS.scrollTo({ left: 0, top: DRAWER.scrollHeight, behavior: "smooth" });
  // Show the drawer
  if (!TOASTS.matches(":open")) TOASTS.showPopover();
  // Dummy types
  let timeoutId;
  if (type === TOAST_TYPES.TIMED) {
    timeoutId = setTimeout(
      removeSlice(slice, undefined),
      Math.random() * 4000 + 1000
    );
  }
  // Event Slicing
  slice.addEventListener("click", removeSlice(slice, timeoutId));
  // Toast will either have a timeout or require user intervention
  // When a toast closes, check if the drawer should be closed
  // That is essentially removing the top layer
};

TOASTER.addEventListener("click", makeToast);

External CSS

  1. https://codepen.io/web-dot-dev/pen/XWqWYgB.css
  2. https://codepen.io/web-dot-dev/pen/ZExZWBQ.css

External JavaScript

  1. https://codepen.io/web-dot-dev/pen/XWqWYgB.js
  2. https://codepen.io/web-dot-dev/pen/ZExZWBQ.js