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.enBento 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.
<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.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Put BentoCard children here directly |
columns | number | 3 | Number of columns (1–6) |
stagger | number | 0.08 | Interval between cards (seconds) |
delay | number | 0 | Delay before the reveal starts (seconds) |
once | boolean | true | Plays only the first time the element enters the viewport |
className | string | — | Appended to the grid container's className |
BentoCard
| Prop | Type | Default | Description |
|---|---|---|---|
colSpan | number | 1 | Number of columns spanned horizontally (1–6) |
rowSpan | number | 1 | Number of rows spanned vertically (1–6) |
title | React.ReactNode | — | Card title |
description | React.ReactNode | — | Card description |
children | React.ReactNode | — | Any custom content (charts, images, and so on), placed above the title |
className | string | — | 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.
Hover Expand Gallery
A horizontally squeezing expand gallery — panels change their share of the space on hover or keyboard focus, while the rest collapse and dim.
Image Trail Card
An interactive card that leaves a trail of fading image afterimages along the cursor's path, with in-place fade and drift variants.