← Back to gallery
CSS

Toast Notification Slide Stack

Three CSS techniques for the toast enter motion — a horizontal Side Slide via translateX, a Pop Scale via transform: scale(0.85→1), and a Drop In via translateY. All three share a flex-column stack and prefers-reduced-motion fallback; flex gap handles the restack on dismiss.

toastnotificationtranslateXtranslateYscalestackreduced-motion

Feedback / toast / enter techniques

Toast notification slide stack

Three different CSS techniques for the toast enter motion — a horizontal Side Slide that matches the gallery thumbnail, a Pop Scale that bounces in like a notification badge, and a Drop In that translates from above. All three share the same flex-column stack and prefers-reduced-motion fallback; the technique determines whether toasts ride in from the side, pop in place, or drop from the top.

translateX · slides in from the right edge

Side Slide

New notices ride in from the right with translateX so the column feels like a Slack-style notification rail. Matches the gallery thumbnail directly.

  • translateX
  • side slide
  • corner stack

transform: scale + opacity · ping in place

Pop Scale

No translate — each toast pops in via scale(0.85) → scale(1) with a subtle bounce. The badge-style enter draws attention without sliding the surrounding content.

  • transform: scale
  • badge-style
  • in-place pop

translateY · drops from above

Drop In

New notices fall in from above with translateY. Pairs naturally with a top-center anchor where the content should appear to land into place.

  • translateY
  • drop from top
  • alert tone

Toast inspector

Side Slide

click push
0 / 3
  • translateX
  • side slide
  • corner stack

New notices ride in from the right with translateX so the column feels like a Slack-style notification rail. The side direction reads as "off-screen → on-screen" without any vertical reshuffle, which keeps the rest of the layout calm. Stack order follows DOM order from top; flex gap handles the restack on dismiss.

Helped you ship something? 🐟 Send my cat a churu

<!-- Mount the toast in the DOM, attach an animationend
     listener to remove it once the lifecycle completes. -->

.toast-stack__item {
  /* Lifecycle in one keyframe: enter → hold (auto-dismiss timer) →
     exit. Total = enter 320ms × 2 + hold 3000ms
     = 3640ms. The animation drives the auto-dismiss timing
     directly from CSS — JS only listens for animationend to
     unmount. */
  animation: toast-lifecycle 3.64s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

@keyframes toast-lifecycle {
  0% {
    opacity: 0;
    transform: translateX(32px);
  }
  8.8%, 91.2% {
    opacity: 1;
    transform: translate(0);
  }
  100% {
    opacity: 0;
    transform: translateX(32px);
    pointer-events: none;
  }
}

/* Manual early-dismiss (× button) flips data-state='exit' to short-
   circuit the hold and play the exit immediately. */
.toast-stack__item[data-state='exit'] {
  animation: none;
  opacity: 0;
  transform: translateX(32px);
  transition:
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 320ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .toast-stack__item {
    animation: none;
  }
}

How to make this

A CSS toast notification stack anchors an aria-live region to a viewport corner, stacks notices with flex gap, and animates each toast with transform plus opacity.

html
1<div class="toast-region" role="status" aria-live="polite">  <article class="toast toast--success" style="--toast-delay: 0ms">    <div>      <strong>Saved draft</strong>      <p>Your changes are stored locally.</p>    </div>    <button type="button" aria-label="Dismiss Saved draft">×</button>  </article>  <article class="toast toast--info" style="--toast-delay: 140ms">    <div>      <strong>Deploy queued</strong>      <p>Static export starts after the build.</p>    </div>    <button type="button" aria-label="Dismiss Deploy queued">×</button>  </article></div> <style>.toast-region {20  position: fixed;  inset-block-start: 1rem;  inset-inline-end: 1rem;  display: flex;  flex-direction: column;  gap: .5rem;  align-items: flex-end;27  pointer-events: none;  z-index: 20;}.toast {  pointer-events: auto;  width: min(22rem, calc(100vw - 2rem));  display: grid;  grid-template-columns: 1fr auto;  gap: .75rem;  padding: .75rem .85rem;  border-radius: 12px;  background: #0f172a;  color: #e2e8f0;  border: 1px solid rgba(148, 163, 184, .24);  box-shadow: 0 18px 30px -18px rgba(15, 23, 42, .88);42  transform: translateX(calc(100% + 1rem));43  opacity: 0;  animation: toast-stack-recipe-slide 4s cubic-bezier(.22,1,.36,1)    var(--toast-delay, 0ms) both;}.toast--success { border-color: rgba(52, 211, 153, .58); }.toast--info { border-color: rgba(129, 140, 248, .52); }.toast strong { display: block; font: 700 .9rem/1.2 ui-sans-serif, system-ui; }.toast p { margin: .12rem 0 0; color: rgba(203, 213, 225, .78); }.toast button {  border: 0;  background: transparent;  color: inherit;  cursor: pointer;  font: inherit;}@keyframes toast-stack-recipe-slide {  0% { transform: translateX(calc(100% + 1rem)); opacity: 0; }  12%, 82% { transform: translateX(0); opacity: 1; }  100% { transform: translateX(calc(100% + 1rem)); opacity: 0; }}63@media (prefers-reduced-motion: reduce) {  .toast {    animation: none;    transform: none;    opacity: 1;  }}</style>

Annotated snippet

  1. Line 1The live region is the notification channel. Use status or polite live announcements for non-urgent feedback so screen readers receive the message without being interrupted mid-task.
    PitfallShould toast notifications use aria-live?

    Yes, for status feedback that appears after an action. Use role="status" or aria-live="polite" for normal confirmation. Reserve assertive announcements for urgent errors because they can interrupt screen reader output.

  2. Line 20Logical inset properties anchor the stack to the top inline end. This works better than hard-coded right/left when the UI later supports right-to-left languages.
  3. Line 27The region ignores pointer events so it does not block the page underneath. Each toast opts back into pointer events so its dismiss button remains usable.
    PitfallWhere should a toast stack live in the DOM?

    Place a single toast region near the app root so it is not clipped by local overflow or transformed containers. Individual toasts can still be inserted and removed by feature-level code.

  4. Line 42The toast enters and exits with transform plus opacity. Avoid animating right, left, or margin for slide-in notifications because those touch layout-positioned values.

    Simulated — same translateX motion on both. animation-timing-function: steps(6) dramatises the stutter that animating right or left can produce on slow devices; cubic-bezier stays smooth on the composite layer.

    PitfallWhy animate toast entry with transform?

    Transform-based slide motion avoids layout work and keeps the stack anchor stable. Animating right, left, margin, or width can cause neighboring toasts and page content to recalculate position.

  5. Line 43Staggering with a custom property lets each toast keep the same keyframe while entering at a different time. Real apps usually set this delay when inserting the toast into the stack.

    Three toasts with the same enter keyframe. Without per-toast animation-delay, all three rush in together as one block; staggered delays (0 / 280ms / 560ms) let each land in its own beat.

  6. Line 63Reduced motion pins toasts in their readable state. The live announcement still occurs, but the visual stack no longer sweeps across the screen.
    PitfallWhat should reduced motion do for toast stacks?

    Disable slide, bounce, and exit travel. It is usually fine to keep a quick opacity change, but a fully static readable state is safer for critical system feedback.

Other pitfalls

How long should toast notifications stay visible?
Give short confirmations enough time to read, and avoid auto-dismissing critical errors before the user can inspect them. If a toast contains an action or important failure detail, provide a dismiss button and consider a longer timeout.

Notes

Overview

Toast notifications slide in from a screen edge, hold for a configurable timeout, then dismiss themselves. The stack caps at a small number (3 in this pattern) so the surface never accumulates an unread pile. Each toast lives in an aria-live="polite" region so screen readers announce new toasts without interrupting active focus.

When to use it

Reach for toasts on success confirmations, transient errors, background-task progress updates — ephemeral status that the user does not need to act on. Skip toasts for anything that requires user action (use a modal instead) and for critical errors that must be acknowledged. Skip them on screens where the user is already focused on something time-sensitive — the slide-in becomes a peripheral distraction.

How it works

A single aria-live container is fixed to the viewport corner (typically bottom-right on desktop, top-center on mobile). New toasts are appended as children; CSS sibling selectors plus transform: translateY() shift older toasts upward by a fixed offset per neighbor. The enter animation is a single @keyframes rule that slides translateX from off-screen to zero with an ease-out curve, and the exit animation flips it. A timeout of 4–6 seconds adds the .is-dismissing class; the matching exit keyframe runs and animationend removes the DOM node. Cap the live region at three children — pop the oldest when a fourth arrives.

Production gotchas

Multiple toasts spawned in the same tick will trigger only the first announcement on most screen readers because aria-live="polite" coalesces rapid DOM mutations. Queue toasts behind a ~250ms gap if all need to be heard. Mobile Safari clips fixed elements that sit above the soft keyboard — offset the toast container by the keyboard inset (use visualViewport.height) or anchor toasts to the top on mobile. Animating height on stack shift causes layout thrash; use transform: translateY() instead so the compositor handles the upward shuffle.

Accessibility

Use aria-live="polite" for ordinary confirmations and aria-live="assertive" (or role="alert") only for errors that cannot wait. Dismiss-on-click should not be the only dismiss path — keyboard users need an explicit close button focusable inside the toast. Under prefers-reduced-motion: reduce swap the slide for a 100ms opacity fade so screen-reader announcements still align with the visual appearance.

References

Implementation depth

Toast stacks need ordering rules before motion rules. Decide where new notifications enter, how older ones compress, and which message owns focus or live-region priority before tuning translateY or scale.

Animation should never hide critical status. Errors and destructive confirmations may need persistent placement or explicit dismissal, while reduced motion should snap the stack into readable order.