Bento layouts for mixing product details, media, and proof in one section. Replace the content and keep the rhythm.
Mosaic
A mixed set of media, stats, avatars, branding, and a CTA. It stacks on narrow widths.

Ship interfaces that feel finished
Build your interface with ready made blocks and modern design.
Free
Open source component library
95%
Teams ship faster with reusable blocks.
300+
Blocks and patterns

Rapid development
Drop in blocks and keep shipping.
"use client";import { Avatar, AvatarFallback, AvatarGroup, AvatarImage,} from "@/components/ui/avatar";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Bento01Props = { className?: string;};const AVATARS = [ "/assets/brand/demos/avatars/avatar-1.png", "/assets/brand/demos/avatars/avatar-2.png", "/assets/brand/demos/avatars/avatar-3.png", "/assets/brand/demos/avatars/avatar-4.png", "/assets/brand/demos/avatars/avatar-5.png",] as const;function Mark({ className }: { className?: string }) { return ( <img src="/assets/brand/logo/ui.svg" alt="" className={cn("brightness-0 dark:invert", className)} /> );}export function Bento01({ className }: Bento01Props) { return ( <section className={cn("@container w-full bg-background", className)}> <div className="mx-auto w-full max-w-5xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14"> <div className="grid grid-cols-1 gap-3 @[32rem]:grid-cols-2 @[48rem]:grid-cols-4 @[48rem]:grid-rows-[minmax(11rem,1fr)_minmax(9rem,1fr)_minmax(10rem,1fr)]"> {/* Tall image — full width on mobile, half on tablet, tall on desktop */} <div className="relative min-h-[14rem] overflow-hidden rounded-xl bg-muted @[32rem]:col-span-2 @[32rem]:min-h-[16rem] @[48rem]:col-span-1 @[48rem]:row-span-2"> <img src="/assets/brand/demos/attachments/attachment-1.png" alt="" className="absolute inset-0 size-full object-cover" /> <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/15 to-transparent" /> <p className="absolute inset-x-0 bottom-0 p-4 text-sm font-medium tracking-wide text-white"> Ship interfaces that feel finished </p> </div> {/* Big middle: pitch + CTA */} <div className="flex min-h-[16rem] flex-col justify-between rounded-xl bg-muted p-5 @[32rem]:col-span-2 @[48rem]:row-span-2"> <Mark className="size-8" /> <div className="mt-8 flex flex-1 flex-col justify-end gap-6"> <p className="max-w-sm text-sm font-medium tracking-wide text-foreground"> Build your interface with ready made blocks and modern design. </p> <div> <p className="text-3xl font-medium tracking-wide text-foreground"> Free </p> <p className="mt-1 text-sm tracking-wide text-muted-foreground"> Open source component library </p> <Button type="button" size="sm" className="mt-4"> Get started </Button> </div> </div> </div> {/* Stat */} <div className="flex min-h-[10rem] flex-col justify-between rounded-xl bg-muted p-5 @[32rem]:min-h-[11rem]"> <p className="text-4xl font-medium tracking-wide text-foreground"> 95% </p> <p className="text-sm tracking-wide text-muted-foreground"> Teams ship faster with reusable blocks. </p> </div> {/* Mark tile */} <div className="flex min-h-[10rem] items-center justify-center rounded-xl bg-muted p-5 @[32rem]:min-h-[11rem] @[48rem]:min-h-[9rem] @[48rem]:col-start-4 @[48rem]:row-start-2"> <Mark className="size-12" /> </div> {/* Social proof */} <div className="flex min-h-[10rem] flex-col justify-between rounded-xl bg-muted p-5 @[32rem]:col-span-1"> <div> <p className="text-3xl font-medium tracking-wide text-foreground"> 300+ </p> <p className="mt-1 text-sm tracking-wide text-muted-foreground"> Blocks and patterns </p> </div> <AvatarGroup overlap="sm" className="mt-4"> {AVATARS.map((src) => ( <Avatar key={src} size="sm"> <AvatarImage src={src} alt="" /> <AvatarFallback>?</AvatarFallback> </Avatar> ))} </AvatarGroup> </div> {/* Wide mark — full row on tablet */} <div className="flex min-h-[10rem] items-center justify-center rounded-xl bg-muted p-5 @[32rem]:col-span-1 @[48rem]:col-span-2"> <Mark className="size-14" /> </div> {/* Image + copy — full width on mobile/tablet */} <div className="relative min-h-[12rem] overflow-hidden rounded-xl bg-muted @[32rem]:col-span-2 @[48rem]:col-span-1 @[48rem]:min-h-[10rem]"> <img src="/assets/brand/demos/attachments/attachment-3.png" alt="" className="absolute inset-0 size-full object-cover" /> <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" /> <div className="absolute inset-x-0 bottom-0 p-4"> <p className="text-sm font-medium tracking-wide text-white"> Rapid development </p> <p className="mt-1 text-xs tracking-wide text-white/75"> Drop in blocks and keep shipping. </p> </div> </div> </div> </div> </section> );}Installation
npx @arctis-sh/@arctis-sh/ui@latest add bento-01Usage
import { Bento01 } from "@/components/blocks/bento/bento-01"<Bento01 />Feature bento
Uneven image tiles with concise copy overlays. They stack on narrow widths.

Token ready
Restyle every block with your colors, radii, and type.

Copy and ship
Install a block and drop it into a page.

Compose
Mix sections freely.

Accessible
Keyboard-first defaults.

Rapid development
Ship interfaces that feel finished.

Theme sync
Light and dark out of the box.
"use client";import { cn } from "@/lib/utils";type Bento02Props = { className?: string;};const CELLS = [ { title: "Token ready", description: "Restyle every block with your colors, radii, and type.", image: "/assets/brand/demos/attachments/attachment-1.png", className: "@[32rem]:col-span-2 @[32rem]:row-span-2 @[48rem]:col-span-2 @[48rem]:row-span-2", }, { title: "Copy and ship", description: "Install a block and drop it into a page.", image: "/assets/brand/demos/attachments/attachment-2.png", className: "@[32rem]:col-span-2 @[48rem]:col-span-2", }, { title: "Compose", description: "Mix sections freely.", image: "/assets/brand/demos/attachments/attachment-3.png", className: "@[32rem]:col-span-1 @[48rem]:col-span-1", }, { title: "Accessible", description: "Keyboard-first defaults.", image: "/assets/brand/demos/attachments/attachment-4.png", className: "@[32rem]:col-span-1 @[48rem]:col-span-1", }, { title: "Rapid development", description: "Ship interfaces that feel finished.", image: "/assets/brand/demos/attachments/attachment-5.png", className: "@[32rem]:col-span-2 @[48rem]:col-span-2", }, { title: "Theme sync", description: "Light and dark out of the box.", image: "/assets/brand/demos/attachments/attachment-2.png", className: "@[32rem]:col-span-2 @[48rem]:col-span-2", },] as const;export function Bento02({ className }: Bento02Props) { return ( <section className={cn("@container w-full bg-background", className)}> <div className="mx-auto w-full max-w-5xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14"> <ul className="grid grid-cols-1 gap-3 @[32rem]:grid-cols-4 @[32rem]:auto-rows-[11rem]"> {CELLS.map((cell) => ( <li key={cell.title} className={cn( "group relative min-h-[12rem] overflow-hidden rounded-xl bg-muted @[32rem]:min-h-0", cell.className, )} > <img src={cell.image} alt="" className="absolute inset-0 size-full object-cover transition-transform duration-500 ease-out group-hover:scale-[1.03]" /> <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-t from-black/75 via-black/25 to-transparent" /> <div className="absolute inset-x-0 bottom-0 p-4"> <h3 className="text-sm font-medium tracking-wide text-white"> {cell.title} </h3> <p className="mt-1 text-xs tracking-wide text-white/75"> {cell.description} </p> </div> </li> ))} </ul> </div> </section> );}Installation
npx @arctis-sh/@arctis-sh/ui@latest add bento-02Usage
import { Bento02 } from "@/components/blocks/bento/bento-02"<Bento02 />Glow outline
Outlined tiles with a restrained glow that follows the cursor along the border.

Token ready
Restyle every block with your theme tokens.

Copy and ship
Install a block and drop it into a page.

Compose
Mix sections freely.

Accessible
Keyboard-first defaults.

Rapid development
Ship interfaces that feel finished.

Theme sync
Light and dark out of the box.
"use client";import { useRef, type MouseEvent } from "react";import { cn } from "@/lib/utils";type Bento03Props = { className?: string;};const CELLS = [ { title: "Token ready", description: "Restyle every block with your theme tokens.", image: "/assets/brand/demos/attachments/attachment-1.png", className: "@[32rem]:col-span-2 @[32rem]:row-span-2", }, { title: "Copy and ship", description: "Install a block and drop it into a page.", image: "/assets/brand/demos/attachments/attachment-2.png", className: "@[32rem]:col-span-2", }, { title: "Compose", description: "Mix sections freely.", image: "/assets/brand/demos/attachments/attachment-3.png", className: "", }, { title: "Accessible", description: "Keyboard-first defaults.", image: "/assets/brand/demos/attachments/attachment-4.png", className: "", }, { title: "Rapid development", description: "Ship interfaces that feel finished.", image: "/assets/brand/demos/attachments/attachment-5.png", className: "@[32rem]:col-span-2", }, { title: "Theme sync", description: "Light and dark out of the box.", image: "/assets/brand/demos/attachments/attachment-2.png", className: "@[32rem]:col-span-2", },] as const;/** Visual reach past each tile so gaps between cards can light neighbors. */const GLOW_REACH_PX = 160;const GLOW_RADIUS = "16rem";function GlowTile({ className, title, description, image,}: { className?: string; title: string; description: string; image: string;}) { return ( <li data-glow-tile data-glow="off" className={cn( "relative min-h-[12rem] rounded-xl p-px @[32rem]:min-h-0", "data-[glow=off]:![background:var(--border)]", className, )} style={{ background: `radial-gradient(${GLOW_RADIUS} circle at var(--glow-x, 50%) var(--glow-y, 50%), rgb(120 200 255), rgb(190 140 255 / 0.85), rgb(255 180 140 / 0.4), var(--border) 55%)`, }} > <div className="flex size-full flex-col rounded-[calc(var(--radius-xl)-1px)] bg-background p-2"> <div className="relative min-h-0 flex-1 overflow-hidden rounded-sm bg-muted"> <img src={image} alt="" className="absolute inset-0 size-full object-cover" /> </div> <div className="px-1 pt-3 pb-1"> <h3 className="text-sm font-medium tracking-wide text-foreground"> {title} </h3> <p className="mt-1 text-xs tracking-wide text-muted-foreground"> {description} </p> </div> </div> </li> );}export function Bento03({ className }: Bento03Props) { const gridRef = useRef<HTMLUListElement>(null); function syncGlow(clientX: number, clientY: number) { const grid = gridRef.current; if (!grid) return; const tiles = grid.querySelectorAll<HTMLElement>("[data-glow-tile]"); for (const el of tiles) { const rect = el.getBoundingClientRect(); const w = el.offsetWidth; const h = el.offsetHeight; const x = ((clientX - rect.left) / Math.max(rect.width, 1)) * w; const y = ((clientY - rect.top) / Math.max(rect.height, 1)) * h; el.style.setProperty("--glow-x", `${x}px`); el.style.setProperty("--glow-y", `${y}px`); const near = clientX >= rect.left - GLOW_REACH_PX && clientX <= rect.right + GLOW_REACH_PX && clientY >= rect.top - GLOW_REACH_PX && clientY <= rect.bottom + GLOW_REACH_PX; el.dataset.glow = near ? "on" : "off"; } } function onMove(event: MouseEvent<HTMLUListElement>) { syncGlow(event.clientX, event.clientY); } function onLeave() { const grid = gridRef.current; if (!grid) return; for (const el of grid.querySelectorAll<HTMLElement>("[data-glow-tile]")) { el.dataset.glow = "off"; } } return ( <section className={cn("@container w-full bg-background", className)}> <div className="mx-auto w-full max-w-5xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14"> <ul ref={gridRef} onMouseMove={onMove} onMouseLeave={onLeave} className="grid grid-cols-1 gap-3 @[32rem]:grid-cols-4 @[32rem]:auto-rows-[12.5rem]" > {CELLS.map((cell) => ( <GlowTile key={cell.title} className={cell.className} title={cell.title} description={cell.description} image={cell.image} /> ))} </ul> </div> </section> );}Installation
npx @arctis-sh/@arctis-sh/ui@latest add bento-03Usage
import { Bento03 } from "@/components/blocks/bento/bento-03"<Bento03 />