---
name: bento-grid
description: Use when you want "Professional, modern, structured" - Organizes content into modular card blocks of different sizes.
---

# Bento Grid

> **Category:** Card / Layout  -  **Personality:** Professional, modern, structured
>
> **Best use:** SaaS feature pages, dashboards
>
> **Ratings:** Professional 5/5 - Casual 4/5 - Exotic 2/5 - Visual impact 4/5 - Difficulty 2/5 - Performance cost 1/5

## What it is

A bento grid (named after the compartmentalised Japanese lunchbox) arranges content into a tight mosaic of **rounded cards of deliberately different sizes** — one big "hero" cell anchors the composition while smaller cells orbit it: a stat, an image, an icon feature, a testimonial, an accent CTA. Every cell is the same visual family (same radius, border, padding rhythm) but the **size variation creates hierarchy without any extra styling** — the eye reads the 2x2 block first, then scans the rest. It is the default layout for modern SaaS feature sections and dashboards because it packs a lot of varied information into one calm, structured frame.

It's pure CSS Grid. The whole effect lives in `grid-template-areas` plus a couple of multi-track spans.

## Dependencies / CDN

None - pure CSS. The demo only loads two display/body fonts (optional):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500;1,9..144,600&family=Hanken+Grotesk:wght@400;500;600;700;800&display=swap" rel="stylesheet">
```

## HTML

Cards are plain blocks; each gets a semantic class that maps to a named grid area. **DOM order is the mobile reading order** — the desktop placement comes entirely from the grid, so you're free to order the source for the single-column fallback. (Stat / image / quote / accent / integration cells trimmed for brevity — they follow the same `.bn-card .bn-{area}` pattern.)

```html
<section class="bn-stage">
  <div class="bn-inner">
    <header class="bn-head">
      <span class="bn-eyebrow">The platform</span>
      <h2 class="bn-title">One workspace. <em>Every workflow.</em></h2>
      <p class="bn-lede">Cadence replaces the patchwork of trackers and approval threads your team tolerates — with one structured system everyone can read.</p>
    </header>

    <div class="bn-grid">

      <!-- HERO (2x2 anchor) -->
      <article class="bn-card bn-hero">
        <div>
          <span class="bn-label bn-label--acc">Workflow engine</span>
          <h3>Automate the busywork. Keep the judgment.</h3>
          <p>Route every request, approval, and handoff through rules you can read in plain English.</p>
        </div>
        <div class="bn-flow" aria-hidden="true">
          <div class="bn-stages">
            <span class="bn-node">Intake</span><span class="bn-conn"></span>
            <span class="bn-node bn-on">Review</span><span class="bn-conn"></span>
            <span class="bn-node">Ship</span>
          </div>
          <div class="bn-rule">
            <span class="bn-rule-dot"></span>
            <span class="bn-rule-txt">When a request is <b>approved</b>, notify <b>#ops</b></span>
            <span class="bn-toggle"></span>
          </div>
        </div>
      </article>

      <!-- STAT (1x1) -->
      <article class="bn-card bn-stat">
        <span class="bn-label">Hours saved / week</span>
        <div class="bn-num">1,240</div>
        <span class="bn-delta">&uarr; 38% <span>vs last quarter</span></span>
      </article>

      <!-- IMAGE (2x1) -->
      <article class="bn-card bn-img">
        <img src="assets/img/interior-01.jpg" alt="A modern operations team at work" loading="lazy">
        <span class="bn-img-tag">Scales with you</span>
        <div class="bn-img-cap">From a five-person studio to a five-thousand-seat org.</div>
      </article>

      <!-- ACCENT CELL (1x1) -->
      <article class="bn-card bn-sync">
        <span class="bn-ico" aria-hidden="true"><!-- bolt svg --></span>
        <h4>Real-time sync</h4>
        <p class="bn-desc">Every change lands everywhere your team already works — instantly.</p>
      </article>

      <!-- ...stat / icon-feature / testimonial / integrations cells... -->
    </div>
  </div>
</section>
```

## CSS

The load-bearing part is `.bn-grid` (areas) and `.bn-card` (the shared shell + hover). Everything else is cosmetic per-cell styling.

```css
/* THE BENTO — named areas declare the whole mosaic at a glance.
   "hero" occupies a 2x2 block; the others fall into single cells. */
.bn-grid{
  display:grid;
  gap:14px;
  grid-template-columns:repeat(4,1fr);
  grid-template-rows:repeat(3, minmax(176px, auto));  /* min height + room to grow */
  grid-template-areas:
    "hero hero stat icon"
    "hero hero img  img"
    "quote quote sync intg";
}

/* Each cell just claims its area — no row/column math in the markup. */
.bn-hero {grid-area:hero}
.bn-stat {grid-area:stat}
.bn-icon-card{grid-area:icon}
.bn-img  {grid-area:img}
.bn-quote{grid-area:quote}
.bn-sync {grid-area:sync}
.bn-intg {grid-area:intg}

/* One shared card shell = visual consistency across every size. */
.bn-card{
  position:relative; overflow:hidden;
  display:flex; flex-direction:column;
  background:#fffdf8;
  border:1px solid rgba(28,26,22,.10);
  border-radius:20px;
  padding:22px;
  box-shadow:0 1px 2px rgba(28,26,22,.05),
             0 14px 28px -20px rgba(28,26,22,.45),
             inset 0 1px 0 rgba(255,255,255,.7);
  transition:transform .3s cubic-bezier(.2,.7,.2,1), box-shadow .3s, border-color .3s;
}
/* tasteful lift on hover */
.bn-card:hover{
  transform:translateY(-5px);
  border-color:#d7daff;
  box-shadow:0 2px 6px rgba(28,26,22,.06),
             0 30px 50px -24px rgba(47,61,240,.45),
             inset 0 1px 0 rgba(255,255,255,.7);
}

/* image cell fills its compartment edge-to-edge */
.bn-img{padding:0}
.bn-img img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform .55s cubic-bezier(.2,.7,.2,1)}
.bn-img:hover img{transform:scale(1.05)}

/* the single accent cell carries the only bold colour */
.bn-sync{background:#2f3df0; border-color:transparent; color:#fff}

/* RESPONSIVE — just rewrite the area map. The cards never move in the DOM. */
@media (max-width:880px){            /* tablet: 2 columns */
  .bn-grid{
    grid-template-columns:repeat(2,1fr);
    grid-template-rows:repeat(5, minmax(168px, auto));
    grid-template-areas:
      "hero hero"
      "stat icon"
      "img  img"
      "quote quote"
      "sync intg";
  }
}
@media (max-width:560px){            /* phone: 1 column, cards flow in source order */
  .bn-grid{grid-template-columns:1fr; grid-template-rows:none;
    grid-template-areas:none; grid-auto-rows:auto; gap:12px}
  .bn-card{grid-area:auto}
  .bn-img{min-height:200px}
}

/* respect reduced motion */
@media (prefers-reduced-motion:reduce){
  .bn-card,.bn-card:hover{transform:none}
  .bn-img:hover img{transform:none}
}
```

## JavaScript

Not required. The layout, hover lift, and the optional staggered card entrance (`@keyframes` + per-`:nth-child` `animation-delay`) are all pure CSS.

## How it works

- **`grid-template-areas`** paints the layout as ASCII art: repeating an area name across rows/columns is what makes a cell span. `"hero hero"` over two rows = a 2x2 block. This is far more readable than juggling `grid-column: 1 / 3; grid-row: 1 / 3` on each child.
- **`grid-template-rows: repeat(3, minmax(176px, auto))`** gives every band a floor height (so a near-empty cell still looks substantial) while letting a text-heavy cell grow. The hero, spanning two rows, is therefore at least ~366px tall.
- **`grid-template-columns: repeat(4, 1fr)`** keeps the columns fluid — the whole mosaic scales with its container, no fixed widths.
- **Size variation = hierarchy.** Because all cards share one shell (radius, border, shadow), the *only* thing signalling importance is footprint. The 2x2 hero reads first; the 1x1 cells read as supporting detail.
- **Responsive is a one-liner mindset:** you don't re-place individual cards, you just hand the grid a new `grid-template-areas` string per breakpoint. Setting it to `none` drops all placements so cards flow in DOM order for mobile.

## Customization

- **Re-arrange the mosaic:** edit the `grid-template-areas` strings — that's the entire layout. Want the image top-left? Move `img` there.
- **Density:** change column count (`repeat(3, 1fr)` for a chunkier grid) and the row floor (`minmax(176px, auto)`).
- **Gap & radius:** `gap` and the card `border-radius` set the "bento" rhythm — 12–16px gap with 18–22px radius reads modern; tighten both for a denser dashboard.
- **Theme:** the demo is warm-paper + one cobalt accent (`#2f3df0`). Swap the accent and the `--bn-*` variables; keep the bold colour to a single cell so it stays a focal point, not noise.
- **Cell content:** any cell can hold a stat, chart, image, quote, logo wall, or CTA — only the footprint changes, the shell stays identical.

## Accessibility & performance

- **Performance is essentially free** — CSS Grid is GPU-cheap; the only animated properties are `transform`/`opacity` (compositor-friendly). No layout thrash, no JS.
- **Source order matters for SR/keyboard users.** Grid placement is visual only; screen readers and tab order follow the DOM. Order your cards in a sensible reading sequence (it doubles as the mobile order).
- **Honour reduced motion:** disable the hover lift, image zoom, and entrance stagger under `prefers-reduced-motion: reduce`.
- **Image text contrast:** lay captions over a dark gradient scrim (not the raw photo) so the text stays legible regardless of the image.
- Always set `alt` on meaningful images and `aria-hidden="true"` on decorative glyphs/avatars.

## Gotchas

- **`grid-auto-rows` ≠ `grid-template-rows`.** Rows created *by* `grid-template-areas` are sized by `grid-template-rows`; `grid-auto-rows` only affects *implicit* tracks beyond the template. If your cells collapse to content height, you forgot to size the template rows.
- **Every area string needs the same column count.** `"hero hero stat icon"` is 4 tokens; if one row has 3 and another 5, the declaration is invalid and silently ignored.
- **Area rectangles must be solid.** A named area has to form a complete rectangle — you can't make an L-shape. The grid drops the whole `grid-template-areas` value if any area is non-rectangular.
- **Image cells need a height to fill.** `object-fit:cover` with an absolutely-positioned `<img>` only works because the grid row gives the card a concrete height; on mobile (auto rows) add a `min-height`.
- **Don't span so far it breaks on mobile.** A 2x2 hero is fine; a cell spanning all 4 columns becomes a full-width band the moment you drop to 2 columns — always re-test each breakpoint's area map.
