Back to library
INTERACTION_092

Sticky Mobile CTA.

A CTA bar docks to the bottom of the screen after the hero. Translated fully below the viewport before the trigger; translating the bar into view and hiding it again near the footer CTA once scrolling past the hero by one viewport height.

CATEGORY
Buttons
COMPLEXITY
Easy
TECH
CSS
PLATFORM
Mobile
PERFORMANCE
Light
LIVE PLAYGROUND
$49Get access
CSS / CSS
/* Sticky Mobile CTA — A CTA bar docks to the bottom of the screen after the hero. */
.sticky-mobile-cta {
  --dur: 340ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --stagger: 60ms;
  will-change: transform, opacity;
}

.sticky-mobile-cta > * {
  opacity: 0;
  transform: translateY(14px);
  animation: sticky-mobile-cta-in var(--dur) var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}

/* translated fully below the viewport -> translating the bar into view and hiding it again near the footer CTA */
@keyframes sticky-mobile-cta-in {
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sticky-mobile-cta > * {
    opacity: 1;
    transform: none;
    animation: none;
  }
}
React / TSX
import { useEffect, useRef } from "react";

/** Sticky Mobile CTA — trigger: scrolling past the hero by one viewport height */
export function StickyMobileCTA({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      el.dataset["state"] = "in";
      return;
    }
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          el.dataset["state"] = "in";
          io.unobserve(el);
        }
      },
      { threshold: 0.2 },
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <div ref={ref} className="sticky-mobile-cta" data-state="out">
      {children}
    </div>
  );
}

IF YOU LIKE THIS, STEAL THESE TOO.

INTERACTION_082CSS

Liquid Hover Button

A rounded fill rises from the bottom edge and floods the button.

Buttons / LightView
INTERACTION_084CSS + JS

Cursor Following Glow Button

A soft glow tracks the pointer inside the button surface.

Buttons / LightView
INTERACTION_085CSS

Sliding Label Button

The label slides up and a duplicate takes its place.

Buttons / LightView
INTERACTION_086CSS

Arrow Morph Button

The arrow exits right and re-enters from the left on hover.

Buttons / LightView