Docs
Bento Grid Features

Bento Grid Features

A premium, animated asymmetric grid layout for showcasing features. Built with Framer Motion for smooth entrance and hover effects, featuring glassmorphism and interactive elements.

Everything you need to build amazing products

Powerful features and tools designed for modern development teams. Built to scale with your business.

Developer Tools

Built-in DevTools offer insights, transparency, and easy management of your app's performance.

terminal
~npm install @artifact-ui/core
Installing dependencies...
Added 14 packages in 1.2s
~
Learn more

Beautiful Design

Create stunning interfaces with our comprehensive design system and component library.

View components

Lightning Fast

Optimized performance with edge computing and global CDN distribution.

Check speed

Global Infrastructure

Deploy to 150+ edge locations worldwide for optimal performance everywhere.

View map

Enterprise Security

Bank-level encryption, SOC 2 compliance, and advanced security features.

Security details

Analytics Dashboard

Real-time insights with customizable dashboards and detailed reporting.

Open dashboard

Features

  • Premium Animations - Staggered entrance and smooth hover effects using Framer Motion
  • Glassmorphism - Modern frosted glass look with backdrop blur
  • Asymmetric Layout - Flexible bento grid design
  • Interactive Cards - Hover lifts, glows, and deep shadows
  • Rich Content - Support for icons, custom visuals, and call-to-action links
  • Flexible Sizing - Span columns, rows, or both
  • Responsive - Adapts seamlessly from mobile to desktop
  • TypeScript Support - Full type safety
  • Customizable - Easy to style and extend via className

Installation

Install dependencies

npm install framer-motion lucide-react

Copy and paste the following code into your project.

import { motion } from "framer-motion";
import { LucideIcon } from "lucide-react";
import { ReactNode } from "react";
import { cn } from "@/lib/utils";
 
interface BentoFeature {
  title: string;
  description: string;
  icon?: LucideIcon;
  span?: "col" | "row" | "both";
  visual?: ReactNode;
  className?: string;
  cta?: string;
  href?: string;
}
 
interface FeatureBentoGridProps {
  headline: string;
  description?: string;
  features: BentoFeature[];
  className?: string;
}
 
const BentoCard = ({
  feature,
  className,
}: {
  feature: BentoFeature;
  className?: string;
}) => {
  const Icon = feature.icon;
 
  const getSpanClass = (span?: "col" | "row" | "both") => {
    switch (span) {
      case "col":
        return "md:col-span-2";
      case "row":
        return "md:row-span-2";
      case "both":
        return "md:col-span-2 md:row-span-2";
      default:
        return "";
    }
  };
 
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: "-50px" }}
      transition={{ duration: 0.5, ease: "easeOut" }}
      className={cn(
        "group relative flex flex-col overflow-hidden rounded-3xl border border-zinc-800 bg-zinc-900/50 p-6 backdrop-blur-sm transition-all duration-300 hover:border-zinc-700/50 hover:bg-zinc-900/80 hover:shadow-2xl hover:shadow-zinc-900/20",
        getSpanClass(feature.span),
        className,
      )}
    >
      {/* Gradient Glow Effect */}
      <div className="absolute -right-20 -top-20 h-64 w-64 rounded-full bg-zinc-800/20 blur-3xl transition-all duration-500 group-hover:bg-zinc-700/30" />
      <div className="absolute -bottom-20 -left-20 h-64 w-64 rounded-full bg-zinc-800/20 blur-3xl transition-all duration-500 group-hover:bg-zinc-700/30" />
 
      <div className="relative z-10 flex h-full flex-col justify-between gap-6">
        <div className="flex flex-col gap-4">
          {Icon && (
            <div className="flex h-12 w-12 items-center justify-center rounded-xl border border-zinc-800 bg-zinc-900/80 shadow-inner">
              <Icon className="h-6 w-6 text-zinc-100" />
            </div>
          )}
          <div>
            <h3 className="text-xl font-semibold tracking-tight text-zinc-100">
              {feature.title}
            </h3>
            <p className="mt-2 text-base leading-relaxed text-zinc-400">
              {feature.description}
            </p>
          </div>
        </div>
 
        {feature.visual && (
          <div className="relative mt-auto overflow-hidden rounded-xl border border-zinc-800/50 bg-zinc-950/50">
            {feature.visual}
          </div>
        )}
 
        {feature.cta && (
          <div className="mt-4 flex items-center gap-2 text-sm font-medium text-zinc-300 transition-colors group-hover:text-white">
            {feature.cta}
            <svg
              className="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 FeatureBentoGrid({
  headline,
  description,
  features,
  className,
}: FeatureBentoGridProps) {
  return (
    <section
      className={cn(
        "relative w-full overflow-hidden bg-black py-24 sm:py-32",
        className,
      )}
    >
      {/* Background Pattern */}
      <div className="absolute inset-0 z-0 opacity-30">
        <div className="absolute inset-0 bg-[radial-gradient(#333_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
      </div>
 
      <div className="container relative z-10 mx-auto px-4 md:px-6">
        <div className="mx-auto max-w-3xl space-y-6 text-center">
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.5 }}
            className="text-4xl font-bold tracking-tighter text-white sm:text-5xl md:text-6xl lg:text-7xl"
          >
            {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.1 }}
              className="mx-auto max-w-2xl text-lg text-zinc-400 md:text-xl"
            >
              {description}
            </motion.p>
          )}
        </div>
 
        <div className="mx-auto mt-16 grid max-w-7xl grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
          {features.map((feature, index) => (
            <BentoCard key={index} feature={feature} />
          ))}
        </div>
      </div>
    </section>
  );
}

Usage

Basic Usage

import FeatureBentoGrid from "@/components/ui/feature-bento-grid";
import { Zap, Shield, Users } from "lucide-react";
 
export default function Page() {
  return (
    <FeatureBentoGrid
      headline="Powerful Features"
      description="Everything you need in one place."
      features={[
        {
          title: "Lightning Fast",
          description: "Blazing fast performance with edge computing.",
          icon: Zap,
          cta: "Learn more",
          href: "/speed",
        },
        {
          title: "Secure",
          description: "Enterprise-grade security and compliance.",
          icon: Shield,
          cta: "Security details",
          href: "/security",
        },
        {
          title: "Collaborative",
          description: "Work together seamlessly in real-time.",
          icon: Users,
        },
      ]}
    />
  );
}

With Spanning and Visuals

import { motion } from "framer-motion";
 
<FeatureBentoGrid
  headline="Visual Features"
  features={[
    {
      title: "Dashboard",
      description: "Beautiful analytics dashboard.",
      icon: BarChart,
      span: "col",
      visual: (
        <div className="flex h-24 items-end gap-2 p-4">
          {[40, 70, 45, 80, 55, 90].map((height, i) => (
            <motion.div
              key={i}
              initial={{ height: 0 }}
              whileInView={{ height: `${height}%` }}
              transition={{ duration: 0.5, delay: i * 0.1 }}
              className="w-full rounded-t bg-blue-500"
            />
          ))}
        </div>
      ),
    },
    {
      title: "Security",
      description: "Enterprise-grade protection.",
      icon: Shield,
      span: "row",
      visual: (
        <div className="flex items-center justify-center py-8">
          <div className="relative flex h-24 w-24 items-center justify-center rounded-full border-4 border-green-500/20 bg-green-500/10">
            <Shield className="h-12 w-12 text-green-500" />
          </div>
        </div>
      ),
    },
  ]}
/>;

Props

PropTypeDefaultDescription
headlinestringRequiredSection headline
descriptionstringundefinedOptional description
featuresBentoFeature[]RequiredArray of features
classNamestringundefinedOptional class name for the section

TypeScript Interface

interface FeatureBentoGridProps {
  headline: string;
  description?: string;
  features: BentoFeature[];
  className?: string;
}
 
interface BentoFeature {
  title: string;
  description: string;
  icon?: LucideIcon;
  span?: "col" | "row" | "both";
  visual?: ReactNode;
  className?: string;
  cta?: string;
  href?: string;
}

Span Options

Control how features span across the grid:

  • span: "col" - Spans 2 columns (wider)
  • span: "row" - Spans 2 rows (taller)
  • span: "both" - Spans 2 columns and 2 rows (large)
  • No span - Normal 1x1 size

Grid Behavior

  • Mobile: Single column layout
  • Tablet (md): 2 columns
  • Desktop (lg): 3 columns

Spanning only applies on medium screens and above.

Use Cases

Perfect for:

  • SaaS Feature sections
  • Product capability showcases
  • Interactive portfolio galleries
  • Service offerings with rich visuals
  • Modern landing pages