CTA Banner Section

PreviousNext

Animated call-to-action banner with gradient background and button animations

Docs
uitripledpage

Preview

Loading preview…
components/sections/shadcnui/cta-banner-section.tsx
"use client";

import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
  motion,
  useInView,
  useReducedMotion,
  type Variants,
} from "framer-motion";
import { ArrowRight, Sparkles } from "lucide-react";
import { useId, useMemo, useRef } from "react";

const highlights = [
  { id: "highlight-free", label: "Free Forever", tone: "bg-emerald-400" },
  { id: "highlight-credit", label: "No Credit Card", tone: "bg-sky-400" },
  { id: "highlight-oss", label: "Open Source", tone: "bg-slate-400" },
];

export function CTABannerSection() {
  const ref = useRef<HTMLDivElement | null>(null);
  const isInView = useInView(ref, { once: true, margin: "-10% 0px" });
  const shouldReduceMotion = useReducedMotion();

  const titleId = useId();
  const descriptionId = useMemo(() => `${titleId}-description`, [titleId]);

  const containerVariants: Variants = useMemo(
    () => ({
      hidden: {
        opacity: shouldReduceMotion ? 1 : 0,
        y: shouldReduceMotion ? 0 : 16,
      },
      visible: {
        opacity: 1,
        y: 0,
        transition: shouldReduceMotion
          ? { duration: 0 }
          : {
              duration: 0.6,
              ease: "easeOut",
              staggerChildren: 0.18,
              delayChildren: 0.12,
            },
      },
    }),
    [shouldReduceMotion]
  );

  const itemVariants: Variants = useMemo(
    () => ({
      hidden: {
        opacity: shouldReduceMotion ? 1 : 0,
        y: shouldReduceMotion ? 0 : 24,
        filter: shouldReduceMotion ? "none" : "blur(4px)",
      },
      visible: {
        opacity: 1,
        y: 0,
        filter: "none",
        transition: shouldReduceMotion
          ? { duration: 0 }
          : { type: "spring", stiffness: 120, damping: 18, mass: 0.9 },
      },
    }),
    [shouldReduceMotion]
  );

  const arrowAnimation = shouldReduceMotion
    ? {}
    : {
        animate: { x: [0, 5, 0] },
        transition: { duration: 1.6, repeat: Infinity, ease: "easeInOut" },
      };

  return (
    <section
      ref={ref}
      aria-labelledby={titleId}
      aria-describedby={descriptionId}
      className="w-full px-4 py-16 sm:px-6 lg:px-8"
    >
      <div className="mx-auto max-w-5xl">
        <motion.div
          variants={containerVariants}
          initial="hidden"
          animate={isInView ? "visible" : "hidden"}
        >
          <Card className="relative overflow-hidden rounded-3xl border border-border/60 bg-card/80 shadow-[0_10px_30px_rgba(2,6,23,0.7)] backdrop-blur-md transition-[transform,box-shadow] duration-500 hover:shadow-[0_25px_80px_rgba(15,23,36,0.45)]">
            <div aria-hidden className="pointer-events-none absolute inset-0">
              <motion.div
                className="absolute -top-32 left-1/2 h-64 w-64 -translate-x-1/2 rounded-full bg-primary/30 blur-3xl"
                {...(shouldReduceMotion
                  ? {}
                  : {
                      animate: {
                        opacity: [0.4, 0.75, 0.4],
                        scale: [0.9, 1.05, 0.9],
                      },
                      transition: {
                        duration: 8,
                        repeat: Infinity,
                        ease: "easeInOut",
                      },
                    })}
              />
              <motion.div
                className="absolute bottom-[-20%] right-[-10%] h-72 w-72 rounded-full bg-[rgba(59,130,246,0.45)] blur-[120px]"
                {...(shouldReduceMotion
                  ? {}
                  : {
                      animate: { opacity: [0.3, 0.6, 0.3], rotate: [0, 15, 0] },
                      transition: {
                        duration: 10,
                        repeat: Infinity,
                        ease: "linear",
                      },
                    })}
              />
            </div>
            <CardContent className="relative z-10 flex flex-col gap-10 p-8 text-center sm:p-12">
              <motion.div
                variants={itemVariants}
                className="mx-auto inline-flex items-center gap-3 rounded-full border border-border/60 bg-white/5 px-6 py-3 text-sm font-medium text-[var(--muted-foreground)] backdrop-blur-sm"
              >
                <span
                  aria-hidden
                  className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/20"
                >
                  <Sparkles className="h-5 w-5 text-primary" aria-hidden />
                </span>
                Build fluid, glassmorphic product experiences
              </motion.div>

              <motion.div variants={itemVariants} className="space-y-4">
                <h2
                  id={titleId}
                  className="text-3xl font-semibold text-[var(--muted-foreground)] sm:text-4xl md:text-5xl"
                >
                  Ready to ship motion-rich UIs in minutes?
                </h2>
                <p
                  id={descriptionId}
                  className="mx-auto max-w-2xl text-base text-[var(--muted-foreground)] sm:text-lg"
                >
                  Access a curated library of glassmorphic components,
                  production-grade motion recipes, and accessibility-first
                  patterns. Start building products that feel alive without
                  sacrificing performance.
                </p>
              </motion.div>

              <motion.div
                variants={itemVariants}
                className="flex flex-col items-center justify-center gap-4 sm:flex-row"
              >
                <Button
                  size="lg"
                  className="group gap-2 rounded-full bg-primary px-8 py-6 text-primary-foreground shadow-lg shadow-primary/30 transition-transform duration-300 hover:translate-y-[-2px] hover:shadow-xl"
                >
                  Get Started
                  <motion.span
                    aria-hidden
                    className="inline-flex"
                    {...(arrowAnimation as Variants)}
                  >
                    <ArrowRight className="h-4 w-4" />
                  </motion.span>
                </Button>
                <Button
                  size="lg"
                  variant="outline"
                  className="rounded-full border-border/60 bg-white/5 px-8 py-6 text-[var(--muted-foreground)] hover:bg-white/10"
                >
                  View Documentation
                </Button>
              </motion.div>

              <motion.ul
                variants={itemVariants}
                className="flex flex-wrap items-center justify-center gap-6 text-sm text-[var(--muted-foreground)]"
              >
                {highlights.map((item, index) => (
                  <li key={item.id} className="flex items-center gap-3">
                    <motion.span
                      aria-hidden
                      className={`inline-flex h-2.5 w-2.5 rounded-full ${item.tone}`}
                      {...(shouldReduceMotion
                        ? {}
                        : {
                            initial: { scale: 0 },
                            animate: { scale: 1 },
                            transition: {
                              delay: 0.4 + index * 0.12,
                              type: "spring",
                              stiffness: 220,
                            },
                          })}
                    />
                    <span>{item.label}</span>
                  </li>
                ))}
              </motion.ul>

              <motion.div
                variants={itemVariants}
                className="mx-auto max-w-xl text-xs text-[var(--muted-foreground)]/80"
              >
                Trusted by teams shipping dashboards, finance tools, and modern
                SaaS experiences.
              </motion.div>
            </CardContent>
          </Card>
        </motion.div>
      </div>
    </section>
  );
}

Installation

npx shadcn@latest add @uitripled/cta-banner-section-shadcnui

Usage

Usage varies by registry entry. Refer to the registry docs or source files below for details.