Docs
Feature Grid 3-Column

Feature Grid 3-Column

A premium, interactive 3-column feature grid with spotlight effects and Framer Motion animations. Features a modern glassmorphic design with mouse-following gradients.

Why Choose Us

Built for modern teams

Everything you need to build, ship, and scale your product. Powerful features that help you move faster.

Lightning Fast

Optimized for speed with edge computing, intelligent caching, and global CDN distribution for instant load times.

View performance

Secure by Default

Enterprise-grade security with SOC 2 compliance, end-to-end encryption, and advanced threat protection.

Security overview

AI-Powered

Leverage cutting-edge AI to automate workflows, boost productivity, and make smarter decisions faster.

Explore AI

Deploy Instantly

Push to production in seconds with zero-downtime deployments, automatic rollbacks, and instant previews.

Start deploying

Global Scale

Serve millions of users worldwide with 99.99% uptime guarantee and automatic scaling infrastructure.

View map

Developer Friendly

Clean APIs, comprehensive documentation, and SDKs for every platform. Built by developers, for developers.

Read docs

Features

  • Spotlight Effect - Mouse-following gradient reveal on hover
  • Premium Animations - Staggered entrance and smooth transitions
  • Glassmorphism - Modern frosted glass look with backdrop blur
  • Interactive Elements - Hover scaling and clickable CTA links
  • Responsive Grid - Adapts seamlessly from mobile to desktop
  • Rich Content - Support for icons, titles, descriptions, and actions
  • TypeScript Support - Full type safety
  • Customizable - Easy to style and extend via className

Installation

Install dependencies

npm install framer-motion lucide-react clsx tailwind-merge

Copy and paste the following code into your project.

"use client";
 
import { motion, useMotionTemplate, useMotionValue } from "framer-motion";
import { LucideIcon } from "lucide-react";
import { MouseEvent } from "react";
import { cn } from "@/lib/utils";
 
interface Feature {
  icon: LucideIcon;
  title: string;
  description: string;
  href?: string;
  cta?: string;
}
 
interface FeatureGrid3ColProps {
  badge?: string;
  headline: string;
  description?: string;
  features: Feature[];
  className?: string;
}
 
function FeatureCard({ feature, index }: { feature: Feature; index: number }) {
  const mouseX = useMotionValue(0);
  const mouseY = useMotionValue(0);
 
  function handleMouseMove({ currentTarget, clientX, clientY }: MouseEvent) {
    const { left, top } = currentTarget.getBoundingClientRect();
    mouseX.set(clientX - left);
    mouseY.set(clientY - top);
  }
 
  const Icon = feature.icon;
 
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true }}
      transition={{ duration: 0.5, delay: index * 0.1 }}
      className="group relative border border-zinc-800 bg-zinc-900/50 overflow-hidden rounded-3xl"
      onMouseMove={handleMouseMove}
    >
      <motion.div
        className="pointer-events-none absolute -inset-px rounded-3xl opacity-0 transition duration-300 group-hover:opacity-100"
        style={{
          background: useMotionTemplate`
            radial-gradient(
              650px circle at ${mouseX}px ${mouseY}px,
              rgba(255,255,255,0.1),
              transparent 80%
            )
          `,
        }}
      />
      <div className="relative h-full p-8">
        <div className="mb-6 inline-flex items-center justify-center rounded-xl border border-zinc-800 bg-zinc-900/50 p-3 shadow-lg backdrop-blur-sm transition-transform duration-300 group-hover:scale-110 group-hover:border-zinc-700">
          <Icon className="h-6 w-6 text-zinc-100" />
        </div>
 
        <h3 className="mb-3 text-xl font-semibold text-zinc-100">
          {feature.title}
        </h3>
        <p className="mb-6 text-zinc-400 leading-relaxed">
          {feature.description}
        </p>
 
        {feature.cta && (
          <div className="flex items-center text-sm font-medium text-zinc-300 transition-colors group-hover:text-white">
            {feature.cta}
            <svg
              className="ml-2 h-4 w-4 transition-transform duration-300 group-hover:translate-x-1"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M17 8l4 4m0 0l-4 4m4-4H3"
              />
            </svg>
          </div>
        )}
      </div>
    </motion.div>
  );
}
 
export function FeatureGrid3Col({
  badge,
  headline,
  description,
  features,
  className,
}: FeatureGrid3ColProps) {
  return (
    <section
      className={cn(
        "relative overflow-hidden bg-black py-24 sm:py-32",
        className,
      )}
    >
      {/* Background Gradients */}
      <div className="absolute inset-0 -z-10">
        <div className="absolute left-1/2 top-0 -z-10 h-[1000px] w-[1000px] -translate-x-1/2 rounded-full bg-blue-500/10 blur-[100px]" />
        <div className="absolute bottom-0 right-0 -z-10 h-[800px] w-[800px] rounded-full bg-purple-500/10 blur-[100px]" />
      </div>
 
      <div className="container mx-auto px-6">
        <div className="mx-auto mb-16 max-w-3xl text-center">
          {badge && (
            <motion.div
              initial={{ opacity: 0, y: 20 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.5 }}
              className="mb-6 inline-flex"
            >
              <span className="rounded-full border border-zinc-800 bg-zinc-900/50 px-4 py-1.5 text-sm font-medium text-zinc-300 backdrop-blur-md">
                <span className="mr-2 inline-block h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
                {badge}
              </span>
            </motion.div>
          )}
 
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.5, delay: 0.1 }}
            className="mb-6 text-4xl font-bold tracking-tight text-white sm:text-5xl md:text-6xl"
          >
            {headline}
          </motion.h2>
 
          {description && (
            <motion.p
              initial={{ opacity: 0, y: 20 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.5, delay: 0.2 }}
              className="text-lg text-zinc-400 sm:text-xl"
            >
              {description}
            </motion.p>
          )}
        </div>
 
        <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
          {features.map((feature, index) => (
            <FeatureCard key={index} feature={feature} index={index} />
          ))}
        </div>
      </div>
    </section>
  );
}

Usage

Basic Usage

import FeatureGrid3Col from "@/components/ui/feature-grid-3col";
import { Zap, Shield, Sparkles } from "lucide-react";
 
export default function FeaturesPage() {
  return (
    <FeatureGrid3Col
      headline="Why choose us"
      features={[
        {
          icon: Zap,
          title: "Lightning Fast",
          description: "Optimized for speed and performance.",
        },
        {
          icon: Shield,
          title: "Secure",
          description: "Enterprise-grade security built-in.",
        },
        {
          icon: Sparkles,
          title: "AI-Powered",
          description: "Leverage cutting-edge AI technology.",
        },
      ]}
    />
  );
}

With Badge and CTAs

<FeatureGrid3Col
  badge="Features"
  headline="Everything you need to succeed"
  description="Powerful features to help you build, ship, and scale your product."
  features={[
    {
      icon: Zap,
      title: "Fast",
      description: "Super fast.",
      cta: "Learn more",
      href: "/performance",
    },
    // ...
  ]}
/>

Props

FeatureGrid3ColProps

PropTypeDefaultDescription
badgestringundefinedOptional badge text above headline
headlinestringRequiredMain section headline
descriptionstringundefinedOptional description below headline
featuresFeature[]RequiredArray of feature objects
classNamestringundefinedOptional class name for the section

Feature Object

PropTypeDescription
iconLucideIconIcon component from lucide-react
titlestringFeature title
descriptionstringFeature description
ctastringOptional Call to Action text
hrefstringOptional link URL

TypeScript Interface

import { LucideIcon } from "lucide-react";
 
interface Feature {
  icon: LucideIcon;
  title: string;
  description: string;
  href?: string;
  cta?: string;
}
 
interface FeatureGrid3ColProps {
  badge?: string;
  headline: string;
  description?: string;
  features: Feature[];
  className?: string;
}

Customization

Adjust Spotlight Size

You can customize the spotlight size by modifying the radial-gradient in the FeatureCard component:

style={{
  background: useMotionTemplate`
    radial-gradient(
      650px circle at ${mouseX}px ${mouseY}px,
      rgba(255,255,255,0.1),
      transparent 80%
    )
  `,
}}

Change Animation Timing

Adjust the transition prop in the motion.div to change the entrance animation speed or delay:

transition={{ duration: 0.5, delay: index * 0.1 }}

Use Cases

Perfect for:

  • SaaS Feature sections
  • Product capability showcases
  • Service offerings
  • Benefit displays
  • Value propositions
  • Technology showcases