Cards & Grids

Bento Grid

A bento-box grid layout — cards reveal with a stagger, and on hover the focused card lifts while the rest dim slightly.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add bento-grid.en

New project or installation trouble? Start here →

Bento Grid is a React bento grid layout component built with Tailwind CSS and Motion, made of BentoGrid and BentoCard. Cards span columns and rows with colSpan and rowSpan, reveal one after another as the grid scrolls into view, and the hovered card lifts while the rest fade slightly.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

3
0.08
0
Active edited usageDefault props
<BentoGrid/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • A feature overview on a SaaS landing page, with large cards for headline features and small ones for the rest
  • Product dashboards or year-in-review pages that mix numbers, charts and screenshots in one block
  • An "about" section on a portfolio, combining photos, skills and links
  • Not a good fit: when card heights vary a lot, like a feed of uneven content, use Masonry Grid

Installation

npx webberui@latest add https://webberui.com/r/bento-grid.en.json

Or, once registries are configured in components.json, install it as @webberui/bento-grid.

Usage

import { BentoGrid, BentoCard } from "@/components/ui/bento-grid";

<BentoGrid columns={3}>
  <BentoCard colSpan={2} title="Featured" description="A headline card spanning two columns." />
  <BentoCard rowSpan={2} title="Row-spanning card" description="Takes up two rows vertically." />
  <BentoCard title="Title" description="An ordinary card." />
  <BentoCard title="Custom content">
    <img src="/chart.png" alt="Statistics chart" />
  </BentoCard>
</BentoGrid>

Examples

Feature overview with fixed row height

The grid uses auto-rows-fr by default, so rows share the same height. Passing auto-rows-[180px] through className fixes the row height; tailwind-merge drops the default.

import { BentoGrid, BentoCard } from "@/components/ui/bento-grid";

export function FeatureBento() {
  return (
    <BentoGrid columns={4} className="auto-rows-[180px]">
      <BentoCard
        colSpan={2}
        rowSpan={2}
        title="Real-time collaboration"
        description="Edit together with live cursors and changes."
      >
        <img src="/screens/editor.png" alt="Collaborative editor" className="rounded-lg" />
      </BentoCard>
      <BentoCard title="Version history" description="Every change from the last 90 days." />
      <BentoCard title="Permissions" description="View and edit access per project." />
      <BentoCard colSpan={2} title="API and webhooks" description="Plug into your existing workflow." />
    </BentoGrid>
  );
}

Render cards from a data array

When the content comes from a CMS or a config file, keep the span in the data and map over it.

const stats = [
  { label: "Monthly active users", value: "48,200", colSpan: 2 },
  { label: "Median response time", value: "120 ms" },
  { label: "Uptime", value: "99.95%" },
  { label: "Integrations", value: "36", colSpan: 2 },
];

<BentoGrid columns={3}>
  {stats.map((s) => (
    <BentoCard key={s.label} colSpan={s.colSpan} description={s.label}>
      <p className="text-3xl font-semibold tabular-nums">{s.value}</p>
    </BentoCard>
  ))}
</BentoGrid>

Replay the reveal on every scroll

With once={false} the reveal plays each time the grid enters the viewport; stagger and delay set the pacing.

<BentoGrid columns={3} once={false} stagger={0.12} delay={0.2}>
  <BentoCard title="Data import" description="CSV, Excel and Google Sheets." />
  <BentoCard title="Scheduling" description="Run daily or weekly." />
  <BentoCard title="Report export" description="One-click PDF output." />
</BentoGrid>

Props

BentoGrid

PropTypeDefaultDescription
childrenReact.ReactNode—Put BentoCard children here directly
columnsnumber3Number of columns (1–6)
staggernumber0.08Interval between cards (seconds)
delaynumber0Delay before the reveal starts (seconds)
oncebooleantruePlays only the first time the element enters the viewport
classNamestring—Appended to the grid container's className

BentoCard

PropTypeDefaultDescription
colSpannumber1Number of columns spanned horizontally (1–6)
rowSpannumber1Number of rows spanned vertically (1–6)
titleReact.ReactNode—Card title
descriptionReact.ReactNode—Card description
childrenReact.ReactNode—Any custom content (charts, images, and so on), placed above the title
classNamestring—Appended to the card's className

Accessibility

  • When the user has "reduce motion" enabled at the system level, a static grid renders with no reveal animation and no hover offset
  • Card titles use semantic <h3> tags, so assistive technology can navigate by heading
  • The hover dimming is a purely visual cue (opacity bottoms out at 0.7), the content stays readable, and keyboard operation and focus are unaffected

FAQ

Does it work with the Next.js App Router?

Yes. The source already has "use client", so import it from a Server Component page. The reduced-motion static version only kicks in after mount, so the first render matches the server output and there is no hydration error.

How do I make the column count responsive?

columns outputs a fixed grid-cols-N. Override it with className="grid-cols-1 md:grid-cols-3"; tailwind-merge removes the default column class. Note that colSpan and rowSpan apply at every breakpoint, so a spanning card adds implicit columns on a single-column layout.

Does BentoCard have to sit inside BentoGrid?

It should. On its own a card renders as a static div with no reveal or hover effect.

How do I change the card styling?

className on BentoCard is merged onto the card itself (rounded corners, border, white background and neutral dark-mode colors by default), so you can override the background, border or padding directly.

What are the dependencies?

motion, clsx, tailwind-merge, and the cn helper in @/lib/utils. Without the CLI, use the Manual tab to copy the source.