
Pulse Button CTA
::after pseudo-element animated via box-shadow outward-cast — button text stays perfectly readable through the pulse.Published
16 copy-paste CSS pulse animation templates built as real-world use cases — landing-page CTA button pulse, mobile FAB ripple, video play overlay halo, e-commerce Add-to-Cart hover-triggered double pulse, notification bell badge, live status dot, ECG heartbeat waveform, DevOps server status ring, recording indicator, map pin drop, skeleton loader shimmer, form submitting state, load-more staggered dots, wishlist heart cardiac rhythm, voice search mic listening pulse, and security scan overlay. Every pulse animates only transform, opacity, and box-shadow — the three GPU-compositor-safe properties that keep 60fps on real mobile hardware. Animating raw width, height, margin, or padding would tank INP scores; every demo here avoids that trap. Scoped under .pl-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related12 CSS Ripple Effects16 CSS Bounce Animations30 CSS Keyframe Animations

::after pseudo-element animated via box-shadow outward-cast — button text stays perfectly readable through the pulse.Published

#ff6b6b FAB, mock dashboard content behind (feed cards, avatar bar), FAB positioned absolute; bottom: 24px; right: 24px. The ripple uses dual ::before and ::after pseudo-elements with staggered animation-delay for a continuous concentric outward wave.Published

Published

#fdf6e3 + forest green #166534 palette (Everlane / Allbirds / artisan e-commerce convention) with Playfair Display serif product name. Hover-triggered means resting state is calm; pulse activates only when the user signals intent by hovering, and continues for as long as the pointer stays.Published

#ffffff) with slate ink (#0f172a) and a red badge (#ef4444). Only the badge itself pulses (subtle scale + box-shadow ring), the bell stays still. The pattern signals "new alert" without being distracting because the pulse animates the smallest UI element on the page — an 18px circle — not the entire header.Published

#1a1a1a) streamer-lounge palette with neon lime (#84cc16) status color — Twitch / Discord / OBS-adjacent aesthetic. The dot appears in three real contexts within one demo: header ("12,400 watching"), stream card ("LIVE"), and channel list (with viewer count) — showing the pattern's versatility.Published

#2563eb) + mint (#10b981) trace with clinical white surface (#ffffff) and grid graph background — Epic MyChart / Teladoc / Withings clinician-facing convention. Uses a pure-SVG animated stroke-dashoffset for the trace + a running dot marker.Published

#22c55e) with amber warning (#f59e0b) tile mixed in for realism. Datadog / PagerDuty / Grafana ops-center aesthetic with all-uppercase JetBrains Mono labels, deep-forest surface (#0a3a1f), and hostname / region metadata.Published

#f1f5f9) surface, deep slate ink (#0f172a), and red (#dc2626) recording state. The dot uses a subtle opacity pulse (not scale) because recording indicators must NEVER feel frivolous — this is a data-capture context where users need to trust the app is genuinely recording.Published

#a3b18a) with cross-hatched road overlays, deep navy pin (#1e3a8a) with white shadow, and a soft ETA card overlay in the bottom corner ("7 mins away" pattern from DoorDash / Uber driver-arrived flow).Published

#f8fafc) with light gray placeholders (#e2e8f0) and a bright shimmer highlight (#f8fafc) sweeping diagonally.Published

#6b46c1) accent on soft lavender background (#f5f3ff) — Superhuman / Vercel / Linear signup aesthetic. The pulse is DELIBERATELY calm (not spinning, not bouncing) because form submission is a trust context — the user just gave you their data and needs to feel it's being handled competently.Published

#fafafa) with slate (#475569) dots and light gray ghost cards above (representing already-loaded content). Twitter / Threads / Reddit infinite-scroll aesthetic — quiet, minimal, honest.Published
#fecaca to #fff1f2) with crimson heart (#dc2626) and a small saved-count badge. Pinterest / Airbnb / Instagram save button aesthetic. The keyframe uses cardiac timing (0% → 20% quick scale-up → 40% pull-back → 60% settle) that mimics a real heartbeat's systole-diastole rhythm — the reason this pattern feels alive, not robotic.Published

#4c1d95 to #7c3aed) with white mic icon and glassmorphic modal card. Google Assistant / Alexa / Siri modal aesthetic. The pulse conveys "I'm actively listening, keep speaking."Published
#000000) with toxic-green scan color (#22c55e) and JetBrains Mono uppercase labels. Malwarebytes / Kaspersky / Norton / Trellix scan-in-progress aesthetic — dev-culture technical, not consumer-friendly.Published
transform, opacity, and box-shadow. For a rectangular button, use box-shadow outward-cast: animation: pulse 1.8s infinite; @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(brand, .5); } 70%, 100% { box-shadow: 0 0 0 18px rgba(brand, 0); } }. The 0 0 0 Npx spread grows outward while alpha fades to 0. box-shadow is chosen over outline or scaled pseudo-elements because it preserves the button's border-radius perfectly at every frame — a scaled pseudo-element distorts corners on non-square buttons. For a CIRCULAR element (FAB, dot, play button), use transform: scale on a pseudo-element instead: ::after { content: ''; position: absolute; inset: 0; border-radius: 50%; background: currentColor; animation: ripple 1.8s infinite; } @keyframes ripple { 0% { transform: scale(1); opacity: .5; } 100% { transform: scale(2.2); opacity: 0; } }. The scale is clean on circles because the shape doesn't distort. Both patterns keep the animation compositor-only (no layout, no paint) — the browser can push these frames to the GPU without triggering reflow. This is the difference between smooth 60fps on mid-tier mobile and jerky 15fps on old Android. Every demo in this collection uses one of these two patterns.width, height, margin, or padding forces the browser to run a full layout + paint + composite pipeline on every animation frame — 60 times per second. The layout stage is expensive because ANY size change on ANY element potentially forces the browser to reflow its siblings, its parent, and every descendant. Paint then rebuilds the pixel buffer for every affected region. Composite finally uploads the new pixels to the GPU. On mid-tier mobile (mid-2020s Android, entry iPhones), this pipeline can take 10-16ms per frame — right at the edge of the 16.67ms budget for 60fps. Add one animated pulse ring using width: 20px → 60px and you're at 22ms per frame → 45fps. Add three concurrent pulses → 33ms → 30fps → visible stutter. Now measure your INP (Interaction to Next Paint) — it will hit 200ms+ on real hardware, and Google flags this as "Poor" in Core Web Vitals. Your Search Console will show the regression within days. The compositor-only alternative: transform and opacity skip layout and paint entirely — they run on the GPU compositor thread. box-shadow is technically a paint operation but modern browsers (Chrome 90+, Safari 15+, Firefox 89+) push it to the compositor for animation. So a 30-instance pulse using transform: scale or box-shadow spread runs at consistent 60fps even on 3-year-old Android hardware — because the CPU-side layout thread is never touched. This is not premature optimization; it's the difference between shipping and being penalized in Search Console.animation-play-state: paused, then flip it to running inside the :hover pseudo-class. Example: .btn::after { animation: pulse .8s ease-out 2; animation-play-state: paused; } .btn:hover::after { animation-play-state: running; }. Critically, the animation is DECLARED in the base rule so :hover only flips the play state. If you write animation INSIDE :hover, the animation restarts from 0% on every hover-in and reset feels janky. The animation-iteration-count: 2 gives you a bounded double-pulse (a burst that confirms readiness and then goes quiet); infinite hover pulse is disorienting and reads as "the button is broken." For touch devices where :hover isn't reliable, wrap the trigger in @media (hover: hover) so touch users get the button without the pulse trigger. For click-triggered one-shot pulses (submit confirmation, item added animation), use :active instead of :hover, and switch to animation-iteration-count: 1 so it fires exactly once per click. For focus-triggered pulses (keyboard accessibility), use :focus-visible instead of :focus — the -visible variant fires only for keyboard focus, not mouse-click focus, so mouse users don't see a distracting pulse on every button click. Demo 04 (Add to Cart Hover Pulse) ships the canonical hover-triggered double-pulse pattern with the @media (hover: hover) guard for touch devices.prefers-reduced-motion: reduce should DISABLE the pulse entirely — @media (prefers-reduced-motion: reduce) { .pulse-el::after { animation: none !important; } }. The element stays functional and clickable; the decorative pulse just doesn't animate. For informational pulses that carry meaning (Live status dot, Recording indicator, ECG heartbeat), the pulse cannot fully disappear because it conveys "this is active" or "recording is in progress" — critical information. The fallback here is to keep a STATIC saturated color state that clearly reads as "active" without animation. Example: a Live status dot animates a green pulse ring in normal motion, but in reduced-motion it shows a solid green dot with no ring — the user still sees "this is live," they just don't see it pulsing. This tiered approach follows WCAG 2.3.3 (Animation from Interactions) and respects WCAG 2.2.2 (Pause, Stop, Hide). WCAG 2.2.2 specifically says: any animation over 5 seconds must have a pause control; under 5 seconds is fine without controls. All 16 demos in this collection cycle under 3 seconds, so no pause controls needed — but reduced-motion must still be respected for users with vestibular disorders. Every demo in this collection ships the reduced-motion guard already.::after preserves the circle perfectly. But scaling a rounded-rectangle pseudo-element distorts the corners nonlinearly (the border-radius scales along with everything else, so what was a subtle 10px radius becomes an oversized 22px radius at scale(2.2) — the corners visibly bulge outward). Trade-off summary: box-shadow = shape-safe on ANY element, single ring per animation (needs multiple pseudo-elements for multi-ring). Transform: scale = shape-safe only on circles/squares, works cleanly with dual pseudo-elements for continuous concentric waves. Demos 01, 04 use box-shadow (rectangular buttons); Demos 02, 03 use transform: scale (circular FAB, play button). For a rectangular button with a multi-ring cascade, combine both: box-shadow on the button itself for the outermost ring, transform: scale on staggered pseudo-elements INSIDE the button for inner rings. Rare pattern, useful for dramatic hero CTAs.will-change on every pulse permanently reserves GPU memory. Apply only to elements that are ACTIVELY animating and remove after. (2) Avoid pulsing more than 10-15 elements simultaneously on one page. If your page has 50 product cards each with an Add-to-Cart pulse, only the currently-hovered card's pulse should be running — use the hover-triggered pattern (Demo 04) so 49 pulses are paused at any given moment. (3) For always-on pulses (Live status dots, Recording indicators), reserve them for elements above-the-fold or currently visible. Use IntersectionObserver to pause pulses on elements scrolled out of view: const io = new IntersectionObserver(entries => entries.forEach(e => e.target.style.animationPlayState = e.isIntersecting ? 'running' : 'paused')); io.observe(dot);. This trims a 50-dot ops dashboard down to 8-12 actively animating dots at any moment. (4) On mobile, respect the battery-saver signal: navigator.connection.saveData or matchMedia('(prefers-reduced-data: reduce)') — if either is true, disable all decorative pulses (same as reduced-motion). This is not documented behavior yet, but Chrome ships the signal and battery-conscious users appreciate it.20 hand-coded CSS animated buttons — neon glow, ripple, 3D press, liquid fill, jelly bounce, shine sweep, animated border, moving gradient CTA, text flip, submit success state, add-to-cart progress, download icon, hamburger-to-close, toggle switch, loading spinner inside button, next/prev arrow nav, and ghost button background reveal. Half pure CSS, half lightweight JS for production interactions.
11 pure CSS animation-timeline demos built on the W3C scroll() and view() spec — reading progress bar, view-timeline fade & reveal, shrinking sticky header, stacking cards, horizontal scroll section, parallax hero, container shadow indicators, reverse-scroll columns, text scrim reveal, cover card to sticky header, and image zoom / clip-path wipe. Zero JS, off the main thread, Core Web Vitals safe. Copy-paste ready.
25 hand-coded CSS background animations for SaaS hero sections, developer tool documentation, agency portfolios, gaming and Web3 landing pages, seasonal e-commerce campaigns, and dark-mode dashboards. Covers full-screen gradient shifts, animated mesh gradients, aurora borealis glow, cursor spotlight follow, interactive water ripples, dot grid and diagonal stripe patterns, SVG grain and noise texture overlays, topographic contour lines, layered SVG waves, plasma and lava lamp fluids, bokeh light blur, particle constellation networks, matrix digital rain, synthwave 3D perspective grids, starfield warp speed, CRT scanline overlays, fog and mist drift, morphing organic blobs, floating glassmorphism orbs, halftone dot patterns, starry night skies, floating geometric shapes, rising bubbles, and falling snow. 20 are pure CSS with zero JavaScript; the 5 that need it use vanilla canvas or pointer tracking with no dependencies. Every background animates compositor-only properties so it never blocks the main thread, is scoped under a .bga-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.