BentoGrid.tsx

Modern grid layout component in Plainform for showcasing features with interactive cards

The Bento Grid component creates a modern, Pinterest-style grid layout with interactive cards that reveal call-to-action buttons on hover.

Features

  • Responsive grid layout
  • Interactive hover effects
  • Background media support
  • Icon integration with SvgFinder
  • Smooth animations
  • Customizable card sizes

Basic Usage

app/page.tsx
import { BentoGrid, BentoCard } from '@/components/ui/BentoGrid';

export default function Page() {
  return (
    <BentoGrid>
      <BentoCard
        name="Feature 1"
        className="col-span-3 lg:col-span-1"
        background={<div className="absolute inset-0 bg-gradient-to-br from-brand/20 to-brand/5" />}
        icon="lockIcon"
        description="Secure authentication with Clerk"
        href="/docs/core/authentication"
        cta="Learn more"
      />
      
      <BentoCard
        name="Feature 2"
        className="col-span-3 lg:col-span-2"
        background={<div className="absolute inset-0 bg-gradient-to-br from-purple-500/20 to-purple-500/5" />}
        icon="walletIcon"
        description="Accept payments with Stripe"
        href="/docs/core/payments"
        cta="Get started"
      />
    </BentoGrid>
  );
}

With Images

app/page.tsx
import { BentoGrid, BentoCard } from '@/components/ui/BentoGrid';
import Image from 'next/image';

<BentoGrid>
  <BentoCard
    name="Dashboard"
    className="col-span-3 lg:col-span-2"
    background={
      <Image
        src="/screenshots/dashboard.png"
        alt="Dashboard"
        fill
        className="object-cover opacity-60"
      />
    }
    icon="chartIcon"
    description="Powerful analytics dashboard"
    href="/dashboard"
    cta="View dashboard"
  />
</BentoGrid>

Custom Grid Layout

app/page.tsx
<BentoGrid className="grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
  <BentoCard
    name="Small Card"
    className="col-span-1"
    background={<div className="bg-blue-500/10" />}
    icon="starIcon"
    description="Small feature card"
    href="/feature-1"
    cta="Explore"
  />
  
  <BentoCard
    name="Wide Card"
    className="col-span-1 md:col-span-2"
    background={<div className="bg-green-500/10" />}
    icon="rocketIcon"
    description="Wide feature card"
    href="/feature-2"
    cta="Discover"
  />
  
  <BentoCard
    name="Tall Card"
    className="col-span-1 row-span-2"
    background={<div className="bg-purple-500/10" />}
    icon="zap Icon"
    description="Tall feature card"
    href="/feature-3"
    cta="Learn more"
  />
</BentoGrid>

With Video Background

app/page.tsx
<BentoCard
  name="Video Demo"
  className="col-span-3"
  background={
    <video
      autoPlay
      loop
      muted
      playsInline
      className="absolute inset-0 w-full h-full object-cover opacity-50"
    >
      <source src="/videos/demo.mp4" type="video/mp4" />
    </video>
  }
  icon="playIcon"
  description="Watch our product in action"
  href="/demo"
  cta="Watch demo"
/>

Feature Showcase

app/page.tsx
import { BentoGrid, BentoCard } from '@/components/ui/BentoGrid';

export default function Features() {
  const features = [
    {
      name: 'Authentication',
      icon: 'lockIcon',
      description: 'Secure user authentication with Clerk',
      href: '/docs/core/authentication',
      cta: 'Learn more',
      background: <div className="bg-gradient-to-br from-blue-500/20 to-blue-500/5" />,
      className: 'col-span-3 lg:col-span-1',
    },
    {
      name: 'Payments',
      icon: 'walletIcon',
      description: 'Accept payments with Stripe integration',
      href: '/docs/core/payments',
      cta: 'Get started',
      background: <div className="bg-gradient-to-br from-green-500/20 to-green-500/5" />,
      className: 'col-span-3 lg:col-span-2',
    },
    {
      name: 'Database',
      icon: 'databaseIcon',
      description: 'PostgreSQL with Prisma ORM',
      href: '/docs/core/database',
      cta: 'Explore',
      background: <div className="bg-gradient-to-br from-purple-500/20 to-purple-500/5" />,
      className: 'col-span-3 lg:col-span-2',
    },
    {
      name: 'Email',
      icon: 'emailIcon',
      description: 'Transactional emails with Resend',
      href: '/docs/core/emails',
      cta: 'View docs',
      background: <div className="bg-gradient-to-br from-orange-500/20 to-orange-500/5" />,
      className: 'col-span-3 lg:col-span-1',
    },
  ];

  return (
    <BentoGrid>
      {features.map((feature) => (
        <BentoCard key={feature.name} {...feature} />
      ))}
    </BentoGrid>
  );
}

Implementation

The BentoCard includes hover animations:

components/ui/BentoGrid.tsx
const BentoCard = ({
  name,
  className,
  background,
  icon,
  description,
  href,
  cta,
}: IBentoCardProps) => (
  <div
    className={cn(
      'group relative flex flex-col justify-between overflow-hidden rounded-xl bg-surface',
      '[box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05)]',
      'dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]',
      className
    )}
  >
    <div>{background}</div>
    <div className="p-4">
      <div className="transform-gpu transition-all duration-300 lg:group-hover:-translate-y-10">
        <SvgFinder
          icon={icon}
          size={38}
          className="transform-gpu transition-all duration-300 group-hover:scale-75"
        />
        <h3 className="text-xl font-semibold">{name}</h3>
        <p className="text-neutral-foreground">{description}</p>
      </div>

      <div className="transform-gpu transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 lg:hidden">
        <Button variant="link" asChild>
          <a href={href}>{cta}</a>
        </Button>
      </div>
    </div>
  </div>
);

Props

BentoGrid

interface IBentoGridProps {
  children: ReactNode;
  className?: string;
}

BentoCard

interface IBentoCardProps {
  name: string;              // Card title
  className: string;         // Grid positioning classes
  background: ReactNode;     // Background element (image, video, gradient)
  icon: string;              // Icon name for SvgFinder
  description: string;       // Card description
  href: string;              // Link destination
  cta: string;               // Call-to-action text
}

Responsive Behavior

  • Mobile: Single column, CTA always visible
  • Desktop: Multi-column grid, CTA appears on hover
  • Smooth transitions between breakpoints

Best Practices

  • Use descriptive card names
  • Keep descriptions concise (2-3 lines)
  • Choose appropriate grid spans for content
  • Use consistent background styles
  • Ensure sufficient contrast for readability
  • Test hover interactions on desktop

How is this guide ?

Last updated on

On this page