16 CSS Bounce Animations01 / 16

Pure CSSMIT licensed

Scroll Down Arrow Bounce

A landing-page scroll-down indicator — a chevron arrow bouncing gently downward in an infinite loop, signaling "there's more content below." Deep purple → magenta gradient surface with the arrow as the visual anchor. The canonical Apple / Vercel / Linear landing-page pattern users search for when building marketing pages. Uses translateY with a multi-stop bounce keyframe — never margin-top or padding-top which would trigger layout on every frame.

Published

Live Demo
Try it

The code

<div class="bn-01">
  <p class="bn-01__hint">You're at the top</p>
  <button class="bn-01__scroll" type="button" aria-label="Scroll to next section">
    <span class="bn-01__label">Scroll</span>
    <span class="bn-01__arrow" aria-hidden="true">
      <svg viewBox="0 0 40 40" width="40" height="40" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="10 15 20 25 30 15"/>
      </svg>
    </span>
  </button>
</div>
.bn-01 {
  --ink: #f5f0ff;
  --sub: rgba(245,240,255,.6);
  --accent: #f9a8d4;
  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: 64px 32px;
  background: radial-gradient(60% 60% at 50% 50%, rgba(249,168,212,.18) 0%, transparent 65%), linear-gradient(135deg,#3b0764 0%,#5b21b6 45%,#831843 100%);
  color: var(--ink);
  text-align: center;
}

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

.bn-01 ::selection {
  background: var(--accent);
  color: #3b0764;
}

.bn-01__hint {
  font-size: .9rem;
  color: var(--sub);
  letter-spacing: .06em;
  font-weight: 500;
}

.bn-01__scroll {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  min-width: 88px;
  padding: 14px 24px;
  background: transparent;
  border: none;
  color: var(--ink);
  cursor: pointer;
  font-family: inherit;
  transition: opacity .18s ease;
}

.bn-01__scroll:hover {
  opacity: .75;
}

.bn-01__scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 8px;
  border-radius: 8px;
}

.bn-01__label {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--accent);
}

.bn-01__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  animation: bn-01-bounce 2.4s cubic-bezier(.55,0,.45,1) infinite;
}

@keyframes bn-01-bounce {
  0% {
    transform: translateY(0);
  }

  30% {
    transform: translateY(-14px);
  }

  50% {
    transform: translateY(-4px);
  }

  70% {
    transform: translateY(-10px);
  }

  100% {
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bn-01__arrow {
    animation: bn-01-pulse 3s ease-in-out infinite;
  }

  @keyframes bn-01-pulse {
    0%,
        100% {
      opacity: 1;
    }

    50% {
      opacity: .55;
    }
  }
}
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: Scroll Down Arrow Bounce
Source: https://codefronts.com/motion/css-bounce-animation/scroll-down-arrow-bounce/

A landing-page scroll-down indicator — a chevron arrow bouncing gently downward in an infinite loop, signaling "there's more content below." Deep purple → magenta gradient surface with the arrow as the visual anchor. The canonical Apple / Vercel / Linear landing-page pattern users search for when building marketing pages. Uses translateY with a multi-stop bounce keyframe — never margin-top or padding-top which would trigger layout on every frame.
## HTML
```html
<div class="bn-01">
  <p class="bn-01__hint">You're at the top</p>
  <button class="bn-01__scroll" type="button" aria-label="Scroll to next section">
    <span class="bn-01__label">Scroll</span>
    <span class="bn-01__arrow" aria-hidden="true">
      <svg viewBox="0 0 40 40" width="40" height="40" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="10 15 20 25 30 15"/>
      </svg>
    </span>
  </button>
</div>
```
## CSS
```css
.bn-01 {
  --ink: #f5f0ff;
  --sub: rgba(245,240,255,.6);
  --accent: #f9a8d4;
  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: 64px 32px;
  background: radial-gradient(60% 60% at 50% 50%, rgba(249,168,212,.18) 0%, transparent 65%), linear-gradient(135deg,#3b0764 0%,#5b21b6 45%,#831843 100%);
  color: var(--ink);
  text-align: center;
}

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

.bn-01 ::selection {
  background: var(--accent);
  color: #3b0764;
}

.bn-01__hint {
  font-size: .9rem;
  color: var(--sub);
  letter-spacing: .06em;
  font-weight: 500;
}

.bn-01__scroll {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  min-width: 88px;
  padding: 14px 24px;
  background: transparent;
  border: none;
  color: var(--ink);
  cursor: pointer;
  font-family: inherit;
  transition: opacity .18s ease;
}

.bn-01__scroll:hover {
  opacity: .75;
}

.bn-01__scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 8px;
  border-radius: 8px;
}

.bn-01__label {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--accent);
}

.bn-01__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  animation: bn-01-bounce 2.4s cubic-bezier(.55,0,.45,1) infinite;
}

@keyframes bn-01-bounce {
  0% {
    transform: translateY(0);
  }

  30% {
    transform: translateY(-14px);
  }

  50% {
    transform: translateY(-4px);
  }

  70% {
    transform: translateY(-10px);
  }

  100% {
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bn-01__arrow {
    animation: bn-01-pulse 3s ease-in-out infinite;
  }

  @keyframes bn-01-pulse {
    0%,
        100% {
      opacity: 1;
    }

    50% {
      opacity: .55;
    }
  }
}
```

How this works

The arrow lives inside a real <button aria-label="Scroll to next section"> and animates via a 5-stop @keyframes bn-01-bounce cascade: 0% translateY(0) → 30% translateY(-14px) → 50% translateY(-4px) → 70% translateY(-10px) → 100% translateY(0). Two upward peaks with the second smaller — the classic "decaying bounce" energy signature that reads as physical. Cycle 2.4 seconds with cubic-bezier(.55, 0, .45, 1). Slower than an urgent alert; slower reads as "ambient invitation."

Deep purple → magenta gradient background (#3b0764#831843) — agency / creative-studio landing palette. The demo shows a large centered chevron with a small "SCROLL" label above it and a subtle "You're at the top" hint text so the demo purpose reads at a glance. 44×44px tap target per WCAG 2.5.5. Semantic HTML with real button element and aria-label. On prefers-reduced-motion, the vertical bounce swaps for a gentle opacity pulse.

Make it yours

  • Change bounce height by editing -14px — 8px for subtle, 20px for playful. Any value works because translateY is compositor-safe.
  • Slow the loop by editing animation-duration: 2.4s — 3s reads as more meditative, 1.8s more urgent. Below 1.5s reads as jittery.
  • For horizontal scroll cues (Apple iPad marketing / horizontal-scroll landing pages), swap translateY for translateX with a rightward chevron.
  • Add a fade-out when the user scrolls past the hero — use Intersection Observer to toggle opacity: 0. Prevents the arrow from persisting into second-fold content.
  • For Tailwind: animate-[bn01_2.4s_cubic-bezier(.55,0,.45,1)_infinite] with matching keyframes.bn01 registered in tailwind.config.js. See FAQ #4.

Gotchas — read before shipping

  • NEVER animate margin-top, padding-top, or top to create the bounce — every frame triggers the browser's full layout + paint + composite pipeline. On mid-tier mobile that's 15-20ms per frame, tanking INP scores below 200ms (Google's "Poor" threshold). transform: translateY runs on the GPU compositor thread with zero layout cost.
  • For accessibility, wrap the arrow in a real <button> with an aria-label — not just a <div>. Screen-reader users need to know this is interactive.
  • The 44×44px minimum touch target (WCAG 2.5.5) applies here — even though the chevron icon is only 24px, button padding brings the tappable area to 44×44px. Mobile users with motor impairments miss taps otherwise.
  • Respect prefers-reduced-motion — vestibular-disorder users find bouncing arrows nauseating. The demo replaces the bounce with a subtle opacity pulse (100% → 60% → 100%) that still conveys "there's more below" without vertical motion.
  • For infinite-scroll pages, the scroll indicator must hide once the user has scrolled — otherwise it reads as "there's more" on the second and third fold when actually the page is loading more. Use Intersection Observer on the hero to toggle visibility.
  • The chevron uses stroke-linejoin: round and stroke-linecap: round so the corners don't feel harsh. Sharp corners on animated elements amplify the motion feel; rounded corners feel softer.

Browser support

ChromeSafariFirefoxEdge
45+10+40+45+

transform + @keyframes. Universal, GPU-accelerated.

Techniques used in this demo

Search CodeFronts

Loading…