Bento grid
Tiles of mixed sizes inside one frame, divided by hairlines, each with a visual and copy.
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/bento-grid
One calm look
Every piece reads your theme tokens, in light and dark.
Components
Picked by a person, checked in both themes.
Install
One shadcn command per component.
Prompts
Every page ships a prompt your agent can read.
Tokens only
No hex, no palette classes, so it re-themes with you.
A wide tile, three thirds and another, with mono numbers and hairline rows as visuals.
Three tiles
Visitors
Unique visitors in the last 30 days.
Installs
Components added through the registry.
Saved
Resources bookmarked by signed in people.
Three equal tiles in one row, each with a number.
With a tall tile
The library
Every category in one sidebar, counts beside each.
Search
Cmd K finds anything across every category.
New this week
Fresh picks, marked with one dot.
Saved
Local bookmarks, no account needed.
span="tall" takes a third of the width over two rows.
Usage
import { BentoGrid, BentoTile } from "@/components/bento-grid"
<BentoGrid>
<BentoTile span="2" title="One calm look" description="Every piece reads your theme tokens.">
<img src="/themes.png" alt="" />
</BentoTile>
<BentoTile title="Install" description="One command per component." />
</BentoGrid>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | On BentoTile: the tile title, one short line. | |
| description | ReactNode | On BentoTile: one or two sentences under the title. | |
| span | "1" | "2" | "3" | "tall" | "1" | On BentoTile: a third, two thirds, the full row, or a third over two rows. |
| children | ReactNode | On BentoTile: the visual, drawn above the text and given the spare height. |
Source
import * as React from "react";
import { cn } from "@/lib/utils";
type BentoGridProps = React.ComponentProps<"div"> & {
/** BentoTile elements. */
children: React.ReactNode;
};
/**
* One frame, many tiles. The frame paints the hairlines between tiles through a 1px gap,
* each tile paints its own card fill. Twelve columns from lg, two from md, one below.
*/
export function BentoGrid({ className, ...props }: BentoGridProps) {
return (
<div
data-slot="bento-grid"
className={cn(
"surface-shine grid w-full gap-px overflow-hidden rounded-lg border bg-border md:auto-rows-fr md:grid-cols-2 lg:grid-cols-12",
className,
)}
{...props}
/>
);
}
type Span = "1" | "2" | "3" | "tall";
const SPAN = {
"1": "lg:col-span-4",
"2": "md:col-span-2 lg:col-span-8",
"3": "md:col-span-2 lg:col-span-12",
tall: "md:row-span-2 lg:col-span-4 lg:row-span-2",
} as const;
type BentoTileProps = Omit<React.ComponentProps<"div">, "title"> & {
/** The tile title, one short line. */
title: React.ReactNode;
/** One or two sentences under the title. */
description?: React.ReactNode;
/** How much of the frame the tile takes: a third, two thirds, the full row, or a third over two rows. */
span?: Span;
/** The visual, drawn above the text and given the spare height. */
children?: React.ReactNode;
};
/** One tile: a visual on top, a title and a description below. */
export function BentoTile({ title, description, span = "1", className, children, ...props }: BentoTileProps) {
return (
<div
data-slot="bento-tile"
data-span={span}
className={cn("flex min-w-0 flex-col gap-4 bg-card p-6", SPAN[span], className)}
{...props}
>
{children && <div className="flex min-h-0 flex-1 flex-col">{children}</div>}
<div className="flex flex-col gap-1">
<h3 className="m-0 text-sm leading-5 font-medium text-foreground/85">{title}</h3>
{description && <p className="m-0 text-sm text-muted-foreground">{description}</p>}
</div>
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/bento-grid/prompt.md, or the Copy AI prompt button above.
When to use
- A features section where some points deserve more room than others
- A product overview with one big visual and a few small facts around it
- A stats or highlights block that should read as one surface
When not to use
- Equal points with no visual, which want the feature card grid
- Content that changes length or count at runtime; spans are chosen by hand
- Interactive tiles that link somewhere; wrap your own link inside a tile instead
Agent notes
- Twelve columns from lg: spans 1, 2 and 3 are a third, two thirds and the full row, tall is a third over two rows
- Choose spans so each row adds up to three thirds, or the last row leaves a gap in the frame
- The frame paints bg-border and each tile paints bg-card, so a tile must stay opaque; override the fill, not the gap
- Server components with no state; the visual slot is plain children, keep it decorative or give it its own alt text