Sticky CTA

Call-to-action that remains visible while scrolling.

Scroll this frame — the call to action stays pinned.

Page content keeps flowing underneath it.

More content here to make the frame scrollable.

html

<div class="sticky-demo">
  <div class="sticky-demo__scroll">
    <p>Scroll this frame &mdash; the call to action stays pinned.</p>
    <p>Page content keeps flowing underneath it.</p>
    <p>More content here to make the frame scrollable.</p>
  </div>
  <div class="sticky-bar sticky-bar--bottom-bar" style="background:#241430;">
    <span>Start your free trial</span>
    <button type="button" class="sticky-bar__close" aria-label="Dismiss">&times;</button>
  </div>
</div>
<style>
.sticky-demo { position: relative; height: 200px; overflow: auto; border: 1px solid #24143022; border-radius: 12px; font: 14px system-ui, sans-serif; color: #241430; }
.sticky-demo__scroll { padding: 16px; }
.sticky-demo__scroll p { margin: 0 0 60px; color: #241430aa; }
.sticky-bar { position: sticky; bottom: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; color: #FAF7F2; font-weight: 700; }
.sticky-bar--bottom-bar { width: 100%; padding: 14px 18px; }
.sticky-bar--bottom-right { margin: 0 12px 12px auto; width: fit-content; padding: 12px 18px; border-radius: 999px; }
.sticky-bar__close { background: none; border: none; color: inherit; font-size: 16px; cursor: pointer; }
.sticky-bar__close:hover, .sticky-bar__close:focus-visible { opacity: .7; }
</style>

tailwind

<div class="relative h-[200px] overflow-auto border border-[#24143022] rounded-xl font-sans text-sm text-[#241430]">
  <div class="p-4">
    <p class="m-0 mb-15 text-[#241430]/65">Scroll this frame &mdash; the call to action stays pinned.</p>
    <p class="m-0 mb-15 text-[#241430]/65">Page content keeps flowing underneath it.</p>
    <p class="m-0 mb-15 text-[#241430]/65">More content here to make the frame scrollable.</p>
  </div>
  <div class="sticky-tw sticky-tw--bottom-bar sticky bottom-0 flex items-center justify-between gap-3 text-[#FAF7F2] font-bold" style="background:#241430;">
    <span>Start your free trial</span>
    <button type="button" class="bg-transparent border-0 text-inherit text-base cursor-pointer hover:opacity-70" aria-label="Dismiss">&times;</button>
  </div>
</div>
<style>
.sticky-tw--bottom-bar { width: 100%; padding: 14px 18px; }
.sticky-tw--bottom-right { margin: 0 12px 12px auto; width: fit-content; padding: 12px 18px; border-radius: 999px; }
</style>

react

export function StickyCta({
  ctaText = "Start your free trial",
  position = "bottom-bar",
  accent = "#241430",
  showDismiss = true,
}) {
  const corner = position === "bottom-right";
  return (
    <div style={{ position: "relative", height: 200, overflow: "auto", border: "1px solid #24143022",
      borderRadius: 12, font: "14px system-ui, sans-serif", color: "#241430" }}>
      <div style={{ padding: 16 }}>
        <p style={{ margin: "0 0 60px", color: "#241430aa" }}>Scroll this frame &mdash; the call to action stays pinned.</p>
        <p style={{ margin: "0 0 60px", color: "#241430aa" }}>Page content keeps flowing underneath it.</p>
        <p style={{ margin: "0 0 60px", color: "#241430aa" }}>More content here to make the frame scrollable.</p>
      </div>
      <div style={{ position: "sticky", bottom: 0, display: "flex", alignItems: "center", justifyContent: "space-between",
        gap: 12, color: "#FAF7F2", fontWeight: 700, background: accent,
        width: corner ? "fit-content" : "100%", padding: corner ? "12px 18px" : "14px 18px",
        borderRadius: corner ? 999 : 0, margin: corner ? "0 12px 12px auto" : 0 }}>
        <span>{ctaText}</span>
        {showDismiss && <button type="button" aria-label="Dismiss" style={{ background: "none", border: "none", color: "inherit", fontSize: 16, cursor: "pointer" }}>&times;</button>}
      </div>
    </div>
  );
}