Feature bento
A feature section laid out as a bento of mixed tiles on shared hairlines.
npx shadcn@latest add https://ui.goodtabs.co/r/feature-bento.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/feature-bento
Small parts, one system
- Go to components
- Copy install command
- Toggle theme
Command menu
Search, jump and run actions from one input.
1.2 kB
Install size
Per component, gzipped.
4.5:1
Contrast
AA on every pair.
npx shadcn add @goodtabs/heroCopy the source
No package to upgrade, the file is yours.
One 2x2 cell beside two numbers and a wide cell.
Tall and wide
One page, four jobs
Mix cell sizes to set the reading order.
Realtime
Updates land in under 200 ms.
- Owner
- Editor
- Viewer
Roles
Owner, editor and viewer.
99.98%
Uptime
Last 90 days.
34
Regions
Edge locations, closest one answers.
9 min
Support
Median reply.
A wide chart cell, a tall list cell and two numbers.
Usage
import { FeatureBento, BentoCell } from "@/components/feature-bento"
<FeatureBento title="Small parts, one system">
<BentoCell size="big" title="Command menu" description="Search and run actions.">...</BentoCell>
<BentoCell title="Install size" description="Per component.">1.2 kB</BentoCell>
</FeatureBento>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | Optional section heading. | |
| description | ReactNode | Optional grey lead under the heading. | |
| children | ReactNode | BentoCell elements. | |
| size (BentoCell) | "small" | "wide" | "tall" | "big" | "small" | Footprint in columns by rows: small 1x1, wide 2x1, tall 1x2, big 2x2. |
Source
feature-bento/feature-bento.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
type FeatureBentoProps = Omit<React.ComponentProps<"section">, "title"> & {
/** Optional section heading above the tiles. */
title?: React.ReactNode;
/** Optional grey lead under the heading. */
description?: React.ReactNode;
/** BentoCell elements. */
children: React.ReactNode;
};
/**
* A feature section built as a bento of mixed tiles. One frame paints the hairlines through a 1px gap,
* each cell paints its own fill. Four columns from the container's 2xl width, one below. Server component.
*/
export function FeatureBento({ title, description, className, children, ...props }: FeatureBentoProps) {
return (
<section className={cn("@container w-full py-8 @2xl:py-16", className)} {...props}>
<div className="mx-auto w-full max-w-6xl px-4 @2xl:px-6">
{(title || description) && (
<div className="mb-8 flex max-w-2xl flex-col gap-3 @2xl:mb-12">
{title && (
<h2 className="m-0 text-xl leading-tight font-medium tracking-tight text-balance @2xl:text-3xl">
{title}
</h2>
)}
{description && <p className="m-0 text-sm text-muted-foreground @2xl:text-base">{description}</p>}
</div>
)}
<div className="surface-shine grid gap-px overflow-hidden rounded-lg border bg-border @2xl:grid-cols-4">
{children}
</div>
</div>
</section>
);
}
const SPAN = {
wide: "@2xl:col-span-2",
tall: "@2xl:row-span-2",
big: "@2xl:col-span-2 @2xl:row-span-2",
small: "",
} as const;
type BentoCellProps = Omit<React.ComponentProps<"div">, "title"> & {
/** Cell title, one short line. */
title: React.ReactNode;
/** One sentence under the title. */
description?: React.ReactNode;
/** Footprint: small is 1x1, wide 2x1, tall 1x2, big 2x2 columns by rows. */
size?: keyof typeof SPAN;
/** The visual above the text: a number, a list, a code line, a mini interface. */
children?: React.ReactNode;
};
/** One cell of a FeatureBento: a visual on top, the title and a grey line below. */
export function BentoCell({ title, description, size = "small", className, children, ...props }: BentoCellProps) {
return (
<div
data-slot="bento-cell"
className={cn("flex min-w-0 flex-col justify-between gap-6 bg-card p-5 @2xl:p-6", SPAN[size], className)}
{...props}
>
{children && <div className="flex min-h-0 flex-1 flex-col justify-center">{children}</div>}
<div className="flex flex-col gap-1">
<h3 className="m-0 text-sm 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/feature-bento/prompt.md, or the Copy AI prompt button above.
When to use
- A features section where some points deserve more room than others
- When each cell has a small visual: a number, a list, a code line
- Four to six cells that total a full rectangle
When not to use
- Equal-weight points, which want the feature grid
- The shared-hairline tiles with a title inside the card kit; use the bento grid for that
- Cells with long copy, which stretch their row
Agent notes
- Four columns from the container's @2xl width, one column below, where sizes are ignored
- Cell sizes must tile a rectangle of four columns or the frame shows gaps filled with bg-border
- The frame paints bg-border through a 1px gap and each cell paints bg-card, so cells must stay opaque
- Server component; cells are plain divs, put your own visual in children
The maths
- Grid = 4 columns, 1px gap; cell footprints big 2x2 = 4, wide 2x1 = 2, tall 1x2 = 2, small 1x1 = 1.
- Demo one: 4 + 1 + 1 + 2 = 8 = 4 x 2, so the rectangle fills with no gap.