16 CSS Bounce Animations04 / 16

CSS + JSMIT licensed

Toast Notification Bounce

A dev-tool toast notification that bounces up from the bottom of the frame — Sonner / Radix Toast / react-hot-toast aesthetic (deep-charcoal surface, semantic icon + copy + dismiss action, mono timestamp). The bounce reads as "new event arrived" with spring-settle physics. Dark-mode-first because dev tools live in dark IDEs. JS auto-cycles through success / info / warning states so users see the pattern in action.

Published

Live Demo
Try it

The code

<div class="bn-04">
  <p class="bn-04__label">Toast notification arrives from below</p>
  <div class="bn-04__toast-slot" id="bn-04-slot" role="status" aria-live="polite" aria-atomic="true"></div>
</div>
.bn-04 {
  --page-bg: #0b1220;
  --surface: #111827;
  --surface-hi: #1e2a3d;
  --ink: #f1f5f9;
  --sub: #94a3b8;
  --muted: #64748b;
  --success: #10b981;
  --info: #3b82f6;
  --warn: #f59e0b;
  --line: #1f2a3d;
  font-family: -apple-system,BlinkMacSystemFont,'Inter','Segoe UI',sans-serif;
  width: 100%;
  min-height: 100vh;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 80px;
  padding: 32px;
  background: radial-gradient(70% 50% at 50% 100%, rgba(16,185,129,.08) 0%, transparent 55%), var(--page-bg);
  color: var(--ink);
}

.bn-04 *,
.bn-04 *::before,
.bn-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.bn-04 ::selection {
  background: var(--success);
  color: var(--surface);
}

.bn-04__label {
  font-size: .85rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--sub);
}

.bn-04__toast-slot {
  display: flex;
  justify-content: center;
  min-width: min(400px,100%);
}

.bn-04__toast {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 44px 14px 16px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 20px 40px -12px rgba(0,0,0,.5),0 4px 8px -4px rgba(0,0,0,.3);
  animation: bn-04-toast-in .55s cubic-bezier(.34,1.56,.64,1) both;
  min-width: 320px;
}

.bn-04__toast.is-out {
  animation: bn-04-toast-out .25s cubic-bezier(.65,0,.35,1) forwards;
}

@keyframes bn-04-toast-in {
  0% {
    opacity: 0;
    transform: translateY(140%) scale(.9);
  }

  60% {
    opacity: 1;
    transform: translateY(-8%) scale(1.02);
  }

  80% {
    transform: translateY(2%) scale(.99);
  }

  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes bn-04-toast-out {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  100% {
    opacity: 0;
    transform: translateY(140%) scale(.94);
  }
}

.bn-04__toast-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  margin-top: 1px;
}

.bn-04__toast--success .bn-04__toast-icon {
  background: rgba(16,185,129,.14);
  color: var(--success);
}

.bn-04__toast--info .bn-04__toast-icon {
  background: rgba(59,130,246,.14);
  color: var(--info);
}

.bn-04__toast--warn .bn-04__toast-icon {
  background: rgba(245,158,11,.14);
  color: var(--warn);
}

.bn-04__toast-body {
  flex: 1;
  min-width: 0;
}

.bn-04__toast-title {
  font-size: .9rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -.005em;
  margin-bottom: 2px;
}

.bn-04__toast-desc {
  font-size: .78rem;
  color: var(--sub);
  line-height: 1.45;
}

.bn-04__toast-time {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: .68rem;
  font-weight: 600;
  color: var(--muted);
  margin-top: 4px;
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}

.bn-04__toast-x {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  transition: background .15s ease,color .15s ease;
}

.bn-04__toast-x:hover {
  background: var(--surface-hi);
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .bn-04__toast {
    animation: bn-04-fade .2s ease-out both!important;
  }

  .bn-04__toast.is-out {
    animation: bn-04-fade-out .2s ease-in forwards!important;
  }

  @keyframes bn-04-fade {
    from {
      opacity: 0;
    }

    to {
      opacity: 1;
    }
  }

  @keyframes bn-04-fade-out {
    from {
      opacity: 1;
    }

    to {
      opacity: 0;
    }
  }
}
(function(){
  var root = document.querySelector('.bn-04');
  if (!root) return;
  var slot = root.querySelector('#bn-04-slot');
  if (!slot) return;
  var seq = [
    { type:'success', title:'Payment confirmed', desc:'Charge succeeded · pi_3O2xLm', time:'just now',
      icon:'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>' },
    { type:'info', title:'Webhook received', desc:'payment_intent.succeeded → 200 OK', time:'2s ago',
      icon:'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>' },
    { type:'warn', title:'3D Secure required', desc:'Redirecting customer to auth challenge', time:'5s ago',
      icon:'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>' }
  ];
  var i = 0;
  function render(item){
    slot.innerHTML = '';
    var toast = document.createElement('div');
    toast.className = 'bn-04__toast bn-04__toast--' + item.type;
    toast.innerHTML =
      '<span class="bn-04__toast-icon">' + item.icon + '</span>' +
      '<div class="bn-04__toast-body">' +
        '<p class="bn-04__toast-title">' + item.title + '</p>' +
        '<p class="bn-04__toast-desc">' + item.desc + '</p>' +
        '<p class="bn-04__toast-time">' + item.time + '</p>' +
      '</div>' +
      '<button class="bn-04__toast-x" aria-label="Dismiss">✕</button>';
    slot.appendChild(toast);
    toast.querySelector('.bn-04__toast-x').addEventListener('click', function(){
      toast.classList.add('is-out');
      setTimeout(function(){ if (toast.parentNode) toast.parentNode.removeChild(toast); }, 250);
    });
  }
  function cycle(){ render(seq[i % seq.length]); i++; setTimeout(cycle, 3200); }
  cycle();
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Bounce Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Toast Notification Bounce
Source: https://codefronts.com/motion/css-bounce-animation/toast-notification-bounce/

A dev-tool toast notification that bounces up from the bottom of the frame — Sonner / Radix Toast / react-hot-toast aesthetic (deep-charcoal surface, semantic icon + copy + dismiss action, mono timestamp). The bounce reads as "new event arrived" with spring-settle physics. Dark-mode-first because dev tools live in dark IDEs. JS auto-cycles through success / info / warning states so users see the pattern in action.
## HTML
```html
<div class="bn-04">
  <p class="bn-04__label">Toast notification arrives from below</p>
  <div class="bn-04__toast-slot" id="bn-04-slot" role="status" aria-live="polite" aria-atomic="true"></div>
</div>
```
## CSS
```css
.bn-04 {
  --page-bg: #0b1220;
  --surface: #111827;
  --surface-hi: #1e2a3d;
  --ink: #f1f5f9;
  --sub: #94a3b8;
  --muted: #64748b;
  --success: #10b981;
  --info: #3b82f6;
  --warn: #f59e0b;
  --line: #1f2a3d;
  font-family: -apple-system,BlinkMacSystemFont,'Inter','Segoe UI',sans-serif;
  width: 100%;
  min-height: 100vh;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 80px;
  padding: 32px;
  background: radial-gradient(70% 50% at 50% 100%, rgba(16,185,129,.08) 0%, transparent 55%), var(--page-bg);
  color: var(--ink);
}

.bn-04 *,
.bn-04 *::before,
.bn-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.bn-04 ::selection {
  background: var(--success);
  color: var(--surface);
}

.bn-04__label {
  font-size: .85rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--sub);
}

.bn-04__toast-slot {
  display: flex;
  justify-content: center;
  min-width: min(400px,100%);
}

.bn-04__toast {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 44px 14px 16px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 20px 40px -12px rgba(0,0,0,.5),0 4px 8px -4px rgba(0,0,0,.3);
  animation: bn-04-toast-in .55s cubic-bezier(.34,1.56,.64,1) both;
  min-width: 320px;
}

.bn-04__toast.is-out {
  animation: bn-04-toast-out .25s cubic-bezier(.65,0,.35,1) forwards;
}

@keyframes bn-04-toast-in {
  0% {
    opacity: 0;
    transform: translateY(140%) scale(.9);
  }

  60% {
    opacity: 1;
    transform: translateY(-8%) scale(1.02);
  }

  80% {
    transform: translateY(2%) scale(.99);
  }

  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes bn-04-toast-out {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  100% {
    opacity: 0;
    transform: translateY(140%) scale(.94);
  }
}

.bn-04__toast-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  margin-top: 1px;
}

.bn-04__toast--success .bn-04__toast-icon {
  background: rgba(16,185,129,.14);
  color: var(--success);
}

.bn-04__toast--info .bn-04__toast-icon {
  background: rgba(59,130,246,.14);
  color: var(--info);
}

.bn-04__toast--warn .bn-04__toast-icon {
  background: rgba(245,158,11,.14);
  color: var(--warn);
}

.bn-04__toast-body {
  flex: 1;
  min-width: 0;
}

.bn-04__toast-title {
  font-size: .9rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -.005em;
  margin-bottom: 2px;
}

.bn-04__toast-desc {
  font-size: .78rem;
  color: var(--sub);
  line-height: 1.45;
}

.bn-04__toast-time {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: .68rem;
  font-weight: 600;
  color: var(--muted);
  margin-top: 4px;
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}

.bn-04__toast-x {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  transition: background .15s ease,color .15s ease;
}

.bn-04__toast-x:hover {
  background: var(--surface-hi);
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .bn-04__toast {
    animation: bn-04-fade .2s ease-out both!important;
  }

  .bn-04__toast.is-out {
    animation: bn-04-fade-out .2s ease-in forwards!important;
  }

  @keyframes bn-04-fade {
    from {
      opacity: 0;
    }

    to {
      opacity: 1;
    }
  }

  @keyframes bn-04-fade-out {
    from {
      opacity: 1;
    }

    to {
      opacity: 0;
    }
  }
}
```

## JavaScript
```js
(function(){
  var root = document.querySelector('.bn-04');
  if (!root) return;
  var slot = root.querySelector('#bn-04-slot');
  if (!slot) return;
  var seq = [
    { type:'success', title:'Payment confirmed', desc:'Charge succeeded · pi_3O2xLm', time:'just now',
      icon:'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>' },
    { type:'info', title:'Webhook received', desc:'payment_intent.succeeded → 200 OK', time:'2s ago',
      icon:'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>' },
    { type:'warn', title:'3D Secure required', desc:'Redirecting customer to auth challenge', time:'5s ago',
      icon:'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>' }
  ];
  var i = 0;
  function render(item){
    slot.innerHTML = '';
    var toast = document.createElement('div');
    toast.className = 'bn-04__toast bn-04__toast--' + item.type;
    toast.innerHTML =
      '<span class="bn-04__toast-icon">' + item.icon + '</span>' +
      '<div class="bn-04__toast-body">' +
        '<p class="bn-04__toast-title">' + item.title + '</p>' +
        '<p class="bn-04__toast-desc">' + item.desc + '</p>' +
        '<p class="bn-04__toast-time">' + item.time + '</p>' +
      '</div>' +
      '<button class="bn-04__toast-x" aria-label="Dismiss">✕</button>';
    slot.appendChild(toast);
    toast.querySelector('.bn-04__toast-x').addEventListener('click', function(){
      toast.classList.add('is-out');
      setTimeout(function(){ if (toast.parentNode) toast.parentNode.removeChild(toast); }, 250);
    });
  }
  function cycle(){ render(seq[i % seq.length]); i++; setTimeout(cycle, 3200); }
  cycle();
})();
```

How this works

The toast starts off-screen at transform: translateY(140%) scale(.9) — fully below the viewport with a slight scale-down to feel weightless. On appear, it animates to translateY(0) scale(1) via @keyframes bn-04-toast-in — a 4-stop cascade that mimics a spring landing: 0% translateY(140%) scale(.9) → 60% translateY(-8%) scale(1.02) → 80% translateY(2%) scale(.99) → 100% translateY(0) scale(1). The upward overshoot then downward undershoot creates the spring-landing feel.

Duration 550ms with cubic-bezier(.34, 1.56, .64, 1). Longer than a modal bounce because the toast travels further. The overshoot is DELIBERATELY MODEST — toasts are informational, not celebratory. A dramatic overshoot on every notification reads as too excited; a subtle overshoot reads as "here's your event."

Toast dismisses via a mirror animation: @keyframes bn-04-toast-out slides it back down with a lightweight cubic-bezier (no bounce on exit — toasts leave calmly). Duration 250ms so dismissal feels responsive.

The demo centers the toast in the frame as the hero — semantic icon colors (mint success, sky info, amber warning), JetBrains Mono timestamp, Sonner-style layout. No code editor mockup competing for attention.

Make it yours

  • Change corner anchor by editing the wrapper position — bottom: 24px; right: 24px (Sonner default), top-right for Radix, top-center for Vercel. Match your app's convention.
  • Change bounce intensity for different toast severities — subtle overshoot for info, tighter no-overshoot for errors (errors should feel serious, not playful).
  • Stack multiple toasts by anchoring each new toast translateY(-64px * N) above the previous — Sonner's expand mode. Requires JS to track active toast count.
  • For Tailwind: animate-[bn04in_.55s_cubic-bezier(.34,1.56,.64,1)_both] + keyframes registered in tailwind.config.js. See FAQ #4.
  • Add swipe-to-dismiss on mobile — touchstart + touchmove tracking with translateX follow-cursor, then dismiss if abs(deltaX) > 100. Matches iOS / Android system notification pattern.

Gotchas — read before shipping

  • Auto-dismiss timing MUST match content length — a 5-word success toast reads in 1.5s; a 30-word error toast needs 5-8s. Fixed 3s dismissal is a Level A WCAG violation (SC 2.2.1 timing adjustable).
  • For accessibility, use role='status' + aria-live='polite' — NOT role='alert' (which is aria-live='assertive' and interrupts screen readers mid-sentence). Only errors should use role='alert'; success/info stay polite.
  • Do NOT stack more than 3 toasts visible at once — users can't process more than that. Sonner's maxToasts: 3 is the canonical rule.
  • The overshoot on toast entrance is INTENTIONALLY MODEST — a dramatic bounce (scale 1.15+) on every notification feels "too excited" and gets fatiguing after the 20th toast.
  • The exit animation must be FASTER than the entrance (250ms vs 550ms) — slow exits feel like the toast is fighting you when you dismiss it.
  • For dark-mode-only tools, the deep-charcoal palette is fine. For consumer apps with dark/light modes, you need a light-mode variant — soft off-white surface + darker text. Never invert colors mechanically.
  • For prefers-reduced-motion, keep the toast VISIBLE + auto-dismiss — but replace the bounce entrance with a straight 200ms opacity fade.

Browser support

ChromeSafariFirefoxEdge
45+10+40+45+

transform + opacity + @keyframes. Universal, GPU-accelerated.

Techniques used in this demo

Search CodeFronts

Loading…