A bento-style feature section with six rounded tiles in three sizes. Two tiles are wide, one is tall and dark, and three are regular squares. Each tile has a heading, a sentence or two, and a small visual drawn with Tailwind shapes: a bar chart of open rates, a phone showing a draft, a send time, a stack of reader avatars, two plan rows and a set of tags. On desktop the tiles form a three-column grid, on tablets two columns, and on phones they stack in a single column in reading order.
const opens = [
{ issue: "No. 38", rate: 41, bar: "h-16" },
{ issue: "No. 39", rate: 47, bar: "h-20" },
{ issue: "No. 40", rate: 44, bar: "h-[72px]" },
{ issue: "No. 41", rate: 52, bar: "h-24" },
{ issue: "No. 42", rate: 49, bar: "h-[88px]" },
{ issue: "No. 43", rate: 58, bar: "h-28" },
];
const readers = [
{ initials: "AO", tone: "bg-orange-200 text-orange-950" },
{ initials: "JK", tone: "bg-stone-800 text-white" },
{ initials: "MS", tone: "bg-teal-200 text-teal-950" },
{ initials: "RD", tone: "bg-amber-200 text-amber-950" },
];
const connections = ["Online shop", "Signup forms", "Podcast feed", "Member area", "Spreadsheets", "Webhooks", "Booking pages", "Event tickets", "Surveys"];
const tile = "flex flex-col overflow-hidden rounded-3xl p-7 ring-1 sm:p-8";
const white = "bg-white ring-stone-900/5";
const title = "text-lg font-semibold tracking-tight";
const body = "mt-2 text-sm leading-relaxed text-stone-600";
export default function BentoGridWithSixFeatureTiles() {
return (
<section className="bg-stone-100 px-4 py-16 text-stone-900 sm:px-6 sm:py-24">
<div className="mx-auto max-w-6xl">
<div className="max-w-2xl px-2">
<p className="text-sm font-semibold text-orange-700">Built for independent writers</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight sm:text-4xl">One newsletter tool, six things it does well</h2>
</div>
<div className="mt-10 grid gap-4 md:grid-cols-2 lg:mt-12 lg:grid-cols-3">
{/* 1: wide */}
<article className={`${tile} ${white} md:col-span-2`}>
<div className="grid gap-8 sm:grid-cols-5 sm:items-end">
<div className="sm:col-span-2">
<h3 className={title}>See which issues get read</h3>
<p className={body}>Opens, clicks and replies for every issue, side by side, so you can tell a strong subject line from a lucky week.</p>
</div>
<div aria-hidden="true" className="sm:col-span-3">
<div className="flex items-end gap-2 border-b border-stone-200 sm:gap-3">
{opens.map((o, i) => (
<div key={o.issue} className="flex flex-1 flex-col items-center gap-2">
<span className={`text-xs font-semibold ${i === opens.length - 1 ? "text-orange-700" : "text-stone-500"}`}>{o.rate}%</span>
<span className={`w-full rounded-t-lg ${o.bar} ${i === opens.length - 1 ? "bg-orange-500" : "bg-stone-200"}`} />
</div>
))}
</div>
<div className="mt-2 flex gap-2 sm:gap-3">
{opens.map((o) => (
<span key={o.issue} className="flex-1 text-center text-xs text-stone-500">{o.issue}</span>
))}
</div>
</div>
</div>
</article>
{/* 2: tall, dark */}
<article className="flex flex-col overflow-hidden rounded-3xl bg-stone-900 p-7 text-white sm:p-8 lg:row-span-2">
<h3 className={title}>Write wherever the idea lands</h3>
<p className="mt-2 text-sm leading-relaxed text-stone-300">A quiet editor that saves every keystroke and looks the same on a phone as on a desk.</p>
<div aria-hidden="true" className="-mb-8 mt-8 flex flex-1 items-end justify-center">
<div className="w-full max-w-[260px] rounded-t-[28px] border-x-4 border-t-4 border-stone-700 bg-stone-50 px-5 pb-8 pt-5 text-stone-900">
<div className="mx-auto h-1.5 w-12 rounded-full bg-stone-300" />
<p className="mt-6 text-xs font-medium text-orange-700">Draft, saved just now</p>
<p className="mt-2 text-base font-semibold leading-snug">The bakery that only opens when it rains</p>
<div className="mt-4 space-y-2">
<div className="h-2 rounded-full bg-stone-200" />
<div className="h-2 rounded-full bg-stone-200" />
<div className="h-2 w-4/5 rounded-full bg-stone-200" />
<div className="flex items-center gap-1">
<div className="h-2 w-2/5 rounded-full bg-stone-200" />
<div className="h-4 w-0.5 bg-orange-500" />
</div>
</div>
<div className="mt-6 flex gap-2">
{["B", "I", "H2"].map((k) => (
<span key={k} className="flex h-8 min-w-[32px] items-center justify-center rounded-lg bg-white px-2 text-xs font-semibold text-stone-700 ring-1 ring-stone-200">{k}</span>
))}
</div>
</div>
</div>
</article>
{/* 3 */}
<article className={`${tile} ${white}`}>
<div aria-hidden="true" className="mb-8 flex items-baseline gap-2">
<span className="text-5xl font-semibold tracking-tight text-stone-900">9:40</span>
<span className="text-sm font-medium text-stone-500">Tuesday</span>
</div>
<h3 className={`${title} mt-auto`}>Sent at the hour it gets opened</h3>
<p className={body}>Each reader gets the issue at the time they usually open their mail, in their own time zone.</p>
</article>
{/* 4 */}
<article className={`${tile} bg-orange-50 ring-orange-900/5`}>
<div aria-hidden="true" className="mb-8 flex items-center">
<div className="flex -space-x-2">
{readers.map((r) => (
<span key={r.initials} className={`flex h-11 w-11 items-center justify-center rounded-full text-xs font-semibold ring-4 ring-orange-50 ${r.tone}`}>{r.initials}</span>
))}
</div>
<span className="ml-3 text-sm font-semibold text-orange-800">+ 4,216 readers</span>
</div>
<h3 className={`${title} mt-auto`}>Bring your whole list with you</h3>
<p className={body}>Upload a file from your old tool. Tags, signup dates and unsubscribes come across untouched.</p>
</article>
{/* 5 */}
<article className={`${tile} ${white}`}>
<div aria-hidden="true" className="mb-8 space-y-2">
{[["Free", "Weekly letter", false], ["Supporter", "$6 a month", true]].map(([tier, note, on]) => (
<div key={String(tier)} className={`flex items-center justify-between rounded-xl px-4 py-3 text-sm ${on ? "bg-stone-900 text-white" : "bg-stone-100 text-stone-700"}`}>
<span className="font-semibold">{tier}</span>
<span className={on ? "text-orange-300" : "text-stone-600"}>{note}</span>
</div>
))}
</div>
<h3 className={`${title} mt-auto`}>Paid issues without a second tool</h3>
<p className={body}>Mark an issue as supporters only and the paywall, receipts and renewals are taken care of.</p>
</article>
{/* 6: wide */}
<article className={`${tile} ${white} md:col-span-2`}>
<ul className="mb-8 flex flex-wrap gap-2">
{connections.map((c, i) => (
<li key={c} className={`rounded-full px-4 py-2.5 text-sm font-medium ${i === 0 ? "bg-orange-100 text-orange-900" : "bg-stone-100 text-stone-700"}`}>{c}</li>
))}
</ul>
<h3 className={`${title} mt-auto`}>Connects to what you already run</h3>
<p className={`${body} max-w-lg`}>New customers, form entries and episodes can add readers or start a welcome series on their own.</p>
</article>
</div>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/bento-grid-with-six-feature-tiles.json
A layout of rectangular tiles in different sizes that fit together into one block, named after the compartments of a Japanese lunch box. It is used on landing pages to show several features at once while giving the important ones more space.
Use a CSS grid with a fixed number of columns, then let some children span more than one cell with `col-span-2` or `row-span-2`. Place the spans so every row adds up to the full column count and no gaps are left.
The span classes here only apply from the `md` and `lg` breakpoints. Below that every tile is full width and they stack in source order, so put the most important tile first.
Grid rows stretch their tiles by default. Each tile is a flex column and the heading uses `mt-auto`, which pushes the text to the bottom so the tiles in a row line up even when the visuals differ in height.
Yes, but rebalance the spans. With three columns, seven regular cells plus spans must still fill complete rows, so make one more tile wide or remove a span from an existing one.
Related: Three Column Feature Grid with Icons, Alternating Feature Rows with Illustrations, Stats Band with Four Numbers on Dark, Pricing Table with Monthly and Yearly Toggle.
More feature sections or all free UI components. Start for free to have a full site built around it.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.