# Feature Bento Grid

> A bento of photo tiles, logo tiles, and stat cards on a twelve-column grid, with one call to action

A shadcn/ui block from the [@cortexcn](https://www.cortexcn.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cortexcn/feature-bento-grid-01).

- Page, with a live preview: https://shadcn.exchange/r/cortexcn/feature-bento-grid-01
- Screenshot: https://shadcn.exchange/shot/cortexcn/feature-bento-grid-01/light.png
- Registry item: https://ui.cortexcn.dev/r/feature-bento-grid-01.json
- Listed: 2026-11-05, source last changed 2026-11-05

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/cortexcn/feature-bento-grid-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://ui.cortexcn.dev/r/utils.json`, `button`

## Files

### `registry/blocks/feature-bento-grid-01/components/feature-bento-grid.tsx`

```tsx
import type { ReactNode } from "react";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";

// Cortexcn item "feature-bento-grid-01", revision 3604419d69ba, MIT license.
// https://ui.cortexcn.dev/r/v/feature-bento-grid-01/3604419d69ba.json

interface BentoImage {
  src: string;
  alt: string;
}

interface BentoAction {
  label: string;
  href: string;
}

interface BentoStat {
  value: string;
  /** Smaller text set beside the value, such as "%" or "+". */
  suffix?: string;
  label: string;
}

export interface FeatureBentoGridProps {
  /** The tall photo tile that opens the grid. */
  hero?: { image: BentoImage; label: string };
  /** Caption on the tall tile that shows the logo. */
  logoCaption?: string;
  stat?: BentoStat;
  /** The wide card with a large figure and one call to action. */
  highlight?: { value: string; label: string; action: BentoAction };
  /** The figure over a row of theme swatches. */
  themes?: BentoStat;
  /** The wide photo tile with an icon, title, and two-line caption. */
  closing?: { image: BentoImage; title: string; lines: [string, string] };
  className?: string;
}

/** Sample photo from Unsplash (free to use under the Unsplash License). */
function unsplash(id: string, width: number) {
  return `https://images.unsplash.com/photo-${id}?w=${width}&q=80&auto=format&fit=crop`;
}

const DEFAULT_HERO = {
  image: {
    src: unsplash("1521194263619-39ecc5b55c61", 1000),
    alt: "Sunlight falling in sharp shapes across a concrete wall",
  },
  label: "Designed with intent.",
};

const DEFAULT_STAT: BentoStat = {
  value: "100",
  suffix: "%",
  label: "Open code, copied into your project",
};

const DEFAULT_HIGHLIGHT = {
  value: "1 command",
  label: "Adds any block to your project with the shadcn CLI",
  action: { label: "Browse blocks", href: "#" },
};

const DEFAULT_THEMES: BentoStat = {
  value: "2",
  label: "Themes, light and dark, from one set of tokens",
};

const DEFAULT_CLOSING = {
  image: {
    src: unsplash("1706451822135-e7276f49b06e", 1200),
    alt: "A concrete staircase cut by diagonal light",
  },
  title: "Rapid development",
  lines: ["Build your interface faster", "with ready-to-use components"] as [
    string,
    string,
  ],
};

/** The Cortexcn mark in currentColor: two corner squares and a joined diagonal. */
function CortexcnMark({ className }: { className?: string }) {
  return (
    <svg
      aria-hidden="true"
      className={className}
      fill="currentColor"
      viewBox="0 0 3 3"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path d="M.17 0H.83A.17 .17 0 0 1 1 .17V.83A.17 .17 0 0 1 .83 1H.17A.17 .17 0 0 1 0 .83V.17A.17 .17 0 0 1 .17 0Z" />
      <path d="M2.17 2H2.83A.17 .17 0 0 1 3 2.17V2.83A.17 .17 0 0 1 2.83 3H2.17A.17 .17 0 0 1 2 2.83V2.17A.17 .17 0 0 1 2.17 2Z" />
      <path d="M2.17 0H2.83A.17 .17 0 0 1 3 .17V.83A.17 .17 0 0 1 2.83 1H2.32A.32 .32 0 0 0 2 1.32V1.83A.17 .17 0 0 1 1.83 2H1.32A.32 .32 0 0 0 1 2.32V2.83A.17 .17 0 0 1 .83 3H.17A.17 .17 0 0 1 0 2.83V2.17A.17 .17 0 0 1 .17 2H.68A.32 .32 0 0 0 1 1.68V1.17A.17 .17 0 0 1 1.17 1H1.68A.32 .32 0 0 0 2 .68V.17A.17 .17 0 0 1 2.17 0Z" />
    </svg>
  );
}

function ClockIcon({ className }: { className?: string }) {
  return (
    <svg
      aria-hidden="true"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth="2"
      viewBox="0 0 24 24"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path d="M12 6v6l4 2" />
      <circle cx="12" cy="12" r="10" />
    </svg>
  );
}

const cardClassName =
  "rounded-3xl bg-card text-card-foreground shadow-xs ring-1 ring-foreground/10";

/** A muted tile with the mark in the middle, in place of a product shot. */
function LogoTile({
  className,
  markClassName,
  children,
}: {
  className?: string;
  markClassName?: string;
  children?: ReactNode;
}) {
  return (
    <div
      className={cn(
        "relative flex items-center justify-center overflow-hidden rounded-3xl border bg-muted",
        className,
      )}
    >
      <CortexcnMark className={cn("size-16 text-foreground", markClassName)} />
      {children}
    </div>
  );
}

function Photo({ image, width }: { image: BentoImage; width: number }) {
  return (
    // biome-ignore lint/performance/noImgElement: framework-agnostic registry block
    <img
      alt={image.alt}
      className="absolute inset-0 size-full object-cover"
      decoding="async"
      height={Math.round(width * 0.75)}
      loading="lazy"
      src={image.src}
      width={width}
    />
  );
}

/** Theme tokens drawn as overlapping swatches. */
const SWATCHES = [
  "bg-background",
  "bg-muted",
  "bg-secondary",
  "bg-primary",
  "bg-foreground",
];

export function FeatureBentoGrid({
  hero = DEFAULT_HERO,
  logoCaption = "Build your interface with open code you can read, change, and keep.",
  stat = DEFAULT_STAT,
  highlight = DEFAULT_HIGHLIGHT,
  themes = DEFAULT_THEMES,
  closing = DEFAULT_CLOSING,
  className,
}: FeatureBentoGridProps) {
  return (
    <section className={cn("w-full px-6 py-24 sm:py-32", className)}>
      <div className="mx-auto grid max-w-6xl grid-cols-1 gap-4 md:grid-cols-6 lg:grid-cols-12">
        <div className="relative h-60 overflow-hidden rounded-3xl bg-muted md:col-span-2 md:row-span-2 md:h-[400px] lg:col-span-4 lg:h-full">
          <Photo image={hero.image} width={1000} />
          <div
            aria-hidden="true"
            className="absolute inset-0 bg-linear-to-t from-black/50 via-transparent to-transparent"
          />
          <p className="absolute bottom-6 left-6 z-10 font-medium text-lg text-white">
            {hero.label}
          </p>
          <span className="absolute top-6 right-6 z-10 flex size-10 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm">
            <CortexcnMark className="size-4" />
          </span>
        </div>

        <LogoTile
          className="h-60 md:col-span-2 md:row-span-2 md:h-[400px] lg:col-span-4 lg:h-full"
          markClassName="size-20 lg:size-24"
        >
          <h3 className="absolute right-6 bottom-6 left-6 text-balance font-medium text-sm leading-tight md:text-base lg:text-xl">
            {logoCaption}
          </h3>
        </LogoTile>

        <div
          className={cn(
            cardClassName,
            "flex flex-col justify-center gap-3 p-4 md:col-span-2 md:h-[192px] md:p-6 lg:col-span-2",
          )}
        >
          <p className="font-bold text-4xl tracking-tight lg:text-6xl">
            {stat.value}
            {stat.suffix && (
              <span className="align-top text-2xl md:text-xl lg:text-3xl">
                {stat.suffix}
              </span>
            )}
          </p>
          <p className="text-sm leading-tight">{stat.label}</p>
        </div>

        <LogoTile
          className="h-60 md:col-span-2 md:h-[192px] lg:col-span-2"
          markClassName="size-10"
        />

        <div className="flex flex-col justify-end gap-2 rounded-3xl bg-muted p-4 shadow-xs ring-1 ring-foreground/10 md:col-span-4 md:h-[300px] md:p-5 lg:col-span-4">
          <p className="text-4xl tracking-tight md:text-5xl lg:text-6xl">
            {highlight.value}
          </p>
          <p className="text-muted-foreground">{highlight.label}</p>
          <a
            className={cn(buttonVariants(), "mt-2 self-start")}
            href={highlight.action.href}
          >
            {highlight.action.label}
          </a>
        </div>

        <div
          className={cn(
            cardClassName,
            "flex flex-col justify-center gap-4 p-4 md:col-span-2 md:h-[300px] md:p-5 lg:col-span-3",
          )}
        >
          <div>
            <p className="font-bold text-4xl tracking-tight md:text-3xl lg:text-6xl">
              {themes.value}
              {themes.suffix && (
                <span className="align-top text-2xl md:text-xl lg:text-3xl">
                  {themes.suffix}
                </span>
              )}
            </p>
            <p className="mt-3 text-sm">{themes.label}</p>
          </div>
          <div aria-hidden="true" className="flex -space-x-2">
            {SWATCHES.map((swatch) => (
              <span
                className={cn(
                  "size-8 rounded-full border-2 border-card ring-1 ring-foreground/10 lg:size-10",
                  swatch,
                )}
                key={swatch}
              />
            ))}
          </div>
        </div>

        <LogoTile
          className="h-60 md:col-span-3 md:h-[300px] lg:col-span-5"
          markClassName="size-16 lg:size-20"
        />

        <div className="relative h-60 overflow-hidden rounded-3xl bg-muted md:col-span-3 md:h-[300px] lg:col-span-4">
          <Photo image={closing.image} width={1200} />
          <div
            aria-hidden="true"
            className="absolute inset-0 bg-linear-to-r from-black/60 via-black/30 to-transparent"
          />
          <div className="absolute inset-0 z-10 flex items-center p-4 text-white md:p-6">
            <div>
              <div className="mb-2 flex items-center gap-2 md:gap-3">
                <span className="flex size-6 items-center justify-center rounded-full bg-white/20 md:size-7">
                  <ClockIcon className="size-3 md:size-4" />
                </span>
                <h3 className="font-semibold text-base md:text-lg">
                  {closing.title}
                </h3>
              </div>
              <p className="text-sm opacity-90">
                {closing.lines[0]}
                <br />
                <span className="font-semibold">{closing.lines[1]}</span>
              </p>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}
```
