A hand-tiled monochrome bento grid mixing a statement, a stat, an image, a quote, and a CTA into one composition.
A monochrome bento grid that lays a badge, heading, and description over a six-column tiled composition of five optional cards: a large statement anchor, an inverted stat figure, a framed cover image with a gradient scrim, an editorial pull-quote, and an always-present closing CTA card with a linked button.
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/bento1?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/bento1?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/bento1.tsx.
The installed file exports bento1Demo alongside the block: the exact props behind the preview above. Spread it to get a working bento grid in one line.
import { Bento1, bento1Demo } from "@/components/beste/block/bento1";
export default function Page() {
return <Bento1 {...bento1Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Bento1 } from "@/components/beste/block/bento1";
export default function Page() {
return (
<Bento1
badge={{ label: "At a glance" }}
heading="The studio, in one grid."
description="A snapshot of how the studio works."
statement={{
index: "S/01",
title: "We make monochrome feel loud.",
body: "Systems built from type, contrast, and restraint.",
}}
stat={{ value: "138", label: "Launches shipped", caption: "Since 2014" }}
image={{
src: "/studio.jpg",
alt: "Studio table layered with printed type specimens",
caption: "Inside the type room",
}}
quote={{
quote: "They kept cutting until only the idea was left standing.",
author: "Mara Vance",
role: "Founder, Halden Press",
}}
button={{ label: "Tour the studio", href: "/about" }}
labels={{
ctaTitle: "Want the long version?",
ctaNote: "A 30-minute walkthrough of the whole practice.",
}}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
badge | { label: string } | – | Eyebrow badge rendered above the heading via Badge7 |
heading | string | – | Section heading in the intro column |
description | string | – | Supporting paragraph under the heading |
statement | StatementCard | – | Large anchor card spanning four columns and two rows |
stat | StatCard | – | Inverted figure card on a bg-foreground surface |
image | ImageCard | – | Framed cover image with a gradient scrim and caption |
quote | QuoteCard | – | Editorial pull-quote card with a Quote icon |
button | ActionButton | – | Linked CTA rendered inside the closing card via Button12 |
labels | Bento1Labels | – | Title and note text for the closing CTA card |
className | string | – | Extra classes for the outer <section> |
type StatementCard = {
index: string;
title: string;
body: string;
};
type StatCard = {
value: string;
label: string;
caption: string;
};
type ImageCard = {
src: string;
alt: string;
caption: string;
};
type QuoteCard = {
quote: string;
author: string;
role: string;
};
type ActionButton = {
label: string;
href: string;
};
type Bento1Labels = {
ctaTitle: string;
ctaNote: string;
};statement, stat, image, quote, plus the intro badge, heading, and description) each render only when their prop is present, so the grid gracefully collapses around whatever you omit.<article> is always rendered regardless of props; only its inner heading and note depend on labels, and its Button12 depends on button, so an empty CTA card can still appear if you pass neither.Button12 asChild wrapping a Next.js <Link href={button.href} />, so it always points at a URL rather than firing a handler.md:grid-cols-6 composition with hardcoded spans: statement takes four columns and two rows, stat and image take two columns each, and both quote and the CTA take three columns; the row template pins minmax(220px,auto) heights.stat card inverts colors on a bg-foreground surface with text-background figures, and the image card renders a plain <img> absolutely positioned under a from-foreground/70 gradient with the caption pinned bottom-left.heading, statement.title, and the quote are rendered as plain text, and the quote footer joins author and role with a literal separator, so passing HTML strings will show as escaped text.image element carries a transition-transform duration-500 class but no accompanying hover-scale utility, so the transition is decorative and produces no visible motion on its own.