Skip to content
goodtabs UI
BrowseBrowse components

Bento grid

Tiles of mixed sizes inside one frame, divided by hairlines, each with a visual and copy.

npx shadcn@latest add https://ui.goodtabs.co/r/bento-grid.json

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.

200in the library

Components

Picked by a person, checked in both themes.

1line to install

Install

One shadcn command per component.

10categories

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

12,480people

Visitors

Unique visitors in the last 30 days.

1,284this week

Installs

Components added through the registry.

3,902bookmarks

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.

14added

New this week

Fresh picks, marked with one dot.

38saved

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

PropTypeDefaultDescription
titleReactNodeOn BentoTile: the tile title, one short line.
descriptionReactNodeOn 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.
childrenReactNodeOn BentoTile: the visual, drawn above the text and given the spare height.

Source

bento-grid/bento-grid.tsx
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