Bento Grid — .mv-bento
Pure-CSS bento grid: cards spanning several columns/rows (data-span="2x1"…), a fading visual area, title, description and a call to action revealed on hover; adapts to its container size.
| Category | Layout |
|---|---|
| Type | CSS only (.mv-bento) |
| Status | stable |
| Keywords | bento, grid, features, landing, container-queries |
When to use
- A landing page presents features as an asymmetric grid of tiles that span columns and rows
- Each feature tile pairs a visual with a title and reveals a call to action on hover
- A showcase grid must adapt to its container width rather than the viewport
Avoid when
- A complete features section with heading, numbered index or ledger layout is needed → use Features instead
- A uniform grid of content items linking to details is needed → use Card instead
- The grid holds dense app data such as dashboard widgets; hover-revealed actions hide key controls
Install
node scripts/add.mjs bento-grid --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["bento-grid"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, components/bento-grid/bento-grid.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div id="bento-demo" style="width:100%;max-width:64rem;margin-inline:auto">
<style>
#bento-demo .bd-search { position: absolute; inset: 1rem 1.25rem auto; display: grid; gap: .5rem; }
#bento-demo .bd-input { display: flex; align-items: center; gap: .6rem; height: 2.6rem; padding: 0 .9rem; border: 1px solid var(--mv-border-strong); border-radius: var(--mv-radius-lg); background: var(--mv-bg); box-shadow: var(--mv-shadow-md); font-size: .875rem; color: var(--mv-fg-muted); }
#bento-demo .bd-input b { color: var(--mv-fg); font-weight: 500; }
#bento-demo .bd-input kbd { margin-left: auto; padding: .1rem .4rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xs); font: 500 .6875rem var(--mv-font-mono); color: var(--mv-fg-subtle); }
#bento-demo .bd-results { display: grid; gap: 2px; padding: .35rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface-raised); box-shadow: var(--mv-shadow-lg); }
#bento-demo .bd-row { display: flex; align-items: center; gap: .6rem; padding: .45rem .55rem; border-radius: var(--mv-radius-md); font-size: .8125rem; }
#bento-demo .bd-row[aria-current] { background: var(--mv-accent-subtle); }
#bento-demo .bd-row i { display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: var(--mv-radius-sm); background: var(--mv-bg-muted); font-style: normal; font-size: .75rem; }
#bento-demo .bd-row small { margin-left: auto; color: var(--mv-fg-subtle); }
#bento-demo .bd-notifs { position: absolute; inset: 1.25rem 1rem auto; display: grid; gap: .6rem; }
#bento-demo .bd-notif { display: flex; gap: .7rem; align-items: center; padding: .7rem .8rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xl); background: var(--mv-surface-raised); box-shadow: var(--mv-shadow-md); font-size: .8125rem; line-height: 1.35; }
#bento-demo .bd-notif small { display: block; color: var(--mv-fg-muted); }
#bento-demo .bd-dot { flex: none; display: grid; place-items: center; width: 2.1rem; height: 2.1rem; border-radius: 50%; color: #fff; font-size: .9rem; background: linear-gradient(135deg, oklch(0.72 0.16 var(--hue)), oklch(0.52 0.2 calc(var(--hue) + 30))); }
#bento-demo .bd-cal { position: absolute; inset: 1.25rem 1.25rem auto; display: grid; grid-template-columns: repeat(7, 1fr); gap: .3rem; font-size: .6875rem; text-align: center; color: var(--mv-fg-muted); }
#bento-demo .bd-cal span { display: grid; place-items: center; aspect-ratio: 1; border-radius: var(--mv-radius-sm); background: var(--mv-bg-muted); }
#bento-demo .bd-cal .on { background: var(--mv-accent); color: var(--mv-fg-on-accent); font-weight: 600; }
#bento-demo .bd-cal .soft { background: var(--mv-accent-subtle); color: var(--mv-accent-fg); }
#bento-demo .bd-apps { position: absolute; inset: 1rem -1rem auto; display: grid; grid-template-columns: repeat(9, 3.25rem); gap: .75rem; justify-content: center; rotate: -6deg; }
#bento-demo .bd-app { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; border-radius: 1rem; color: #fff; box-shadow: var(--mv-shadow-md); background: linear-gradient(145deg, oklch(0.74 0.15 var(--hue)), oklch(0.5 0.19 calc(var(--hue) + 30))); }
#bento-demo .bd-app svg { width: 1.5rem; height: 1.5rem; }
#bento-demo .bd-shield { position: absolute; inset: 0; display: grid; place-items: center; padding-bottom: 2.5rem; }
#bento-demo .bd-shield::before, #bento-demo .bd-shield::after { content: ""; position: absolute; width: 11rem; aspect-ratio: 1; border-radius: 50%; border: 1px dashed color-mix(in oklch, var(--mv-accent) 40%, transparent); top: 50%; left: 50%; translate: -50% -70%; }
#bento-demo .bd-shield::after { width: 7rem; border-style: solid; background: radial-gradient(circle, var(--mv-accent-subtle), transparent 70%); }
#bento-demo .bd-shield svg { position: relative; width: 3.5rem; height: 3.5rem; color: var(--mv-accent); translate: 0 -1.2rem; filter: drop-shadow(0 6px 14px color-mix(in oklch, var(--mv-accent) 40%, transparent)); }
</style>
<div class="mv-bento">
<article class="mv-bento-card" data-span="2x1">
<div class="mv-bento-visual" aria-hidden="true">
<div class="bd-search">
<div class="bd-input"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><b>march invoice</b><kbd>⌘K</kbd></div>
<div class="bd-results">
<div class="bd-row" aria-current="true"><i>📄</i>Invoice INV-2026-031 · Nebula Studio<small>Mar 12</small></div>
<div class="bd-row"><i>💬</i>“Did you get the March invoice?”<small>Slack</small></div>
</div>
</div>
</div>
<div class="mv-bento-body">
<svg class="mv-bento-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<h3 class="mv-bento-title">Instant search</h3>
<p class="mv-bento-description">Find any doc, message or file in under 50 ms, wherever it lives.</p>
</div>
<a class="mv-bento-cta" href="#bento-demo">Learn more <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</article>
<article class="mv-bento-card" data-span="1x2">
<div class="mv-bento-visual" aria-hidden="true" style="--mv-bento-visual-height: 78%">
<div class="bd-notifs">
<div class="bd-notif"><span class="bd-dot" style="--hue:150">$</span><span><b>Payment received</b><small>$249.00 · Nebula Studio</small></span></div>
<div class="bd-notif"><span class="bd-dot" style="--hue:260">M</span><span><b>Maya commented</b><small>“Looks great, mockup approved!”</small></span></div>
<div class="bd-notif"><span class="bd-dot" style="--hue:30">!</span><span><b>Traffic spike</b><small>+212% on the Pricing page</small></span></div>
<div class="bd-notif"><span class="bd-dot" style="--hue:200">↗</span><span><b>Deploy complete</b><small>v2.4.0 is live in production</small></span></div>
<div class="bd-notif"><span class="bd-dot" style="--hue:330">+</span><span><b>New sign-up</b><small>Hugo joined the Design team</small></span></div>
</div>
</div>
<div class="mv-bento-body">
<svg class="mv-bento-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 1 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
<h3 class="mv-bento-title">Smart notifications</h3>
<p class="mv-bento-description">Only what matters interrupts you. Everything else waits for your evening digest.</p>
</div>
<a class="mv-bento-cta" href="#bento-demo">Set up alerts <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</article>
<article class="mv-bento-card">
<div class="mv-bento-visual" aria-hidden="true">
<svg viewBox="0 0 300 160" preserveAspectRatio="none" style="position:absolute;inset:auto 0 0;height:75%">
<defs><linearGradient id="bento-demo-area" x1="0" y1="0" x2="0" y2="1"><stop offset="0" style="stop-color:var(--mv-accent);stop-opacity:.35"/><stop offset="1" style="stop-color:var(--mv-accent);stop-opacity:0"/></linearGradient></defs>
<path d="M0 130 C40 120 55 90 90 96 S140 60 170 70 220 30 250 36 285 14 300 10 V160 H0Z" fill="url(#bento-demo-area)"/>
<path d="M0 130 C40 120 55 90 90 96 S140 60 170 70 220 30 250 36 285 14 300 10" fill="none" style="stroke:var(--mv-accent)" stroke-width="2.5" vector-effect="non-scaling-stroke"/>
</svg>
</div>
<div class="mv-bento-body">
<svg class="mv-bento-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg>
<h3 class="mv-bento-title">Live analytics</h3>
<p class="mv-bento-description">Your KPIs, updated every second.</p>
</div>
<a class="mv-bento-cta" href="#bento-demo">View dashboard <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</article>
<article class="mv-bento-card">
<div class="mv-bento-visual" aria-hidden="true">
<div class="bd-cal">
<span>1</span><span>2</span><span class="soft">3</span><span>4</span><span>5</span><span>6</span><span>7</span>
<span>8</span><span class="on">9</span><span>10</span><span class="soft">11</span><span>12</span><span>13</span><span>14</span>
<span>15</span><span>16</span><span>17</span><span class="soft">18</span><span>19</span><span>20</span><span>21</span>
</div>
</div>
<div class="mv-bento-body">
<svg class="mv-bento-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
<h3 class="mv-bento-title">Scheduling</h3>
<p class="mv-bento-description">The right slot, found for the whole team.</p>
</div>
<a class="mv-bento-cta" href="#bento-demo">Book a slot <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</article>
<article class="mv-bento-card" data-span="2x1" data-variant="accent">
<div class="mv-bento-visual" aria-hidden="true">
<div class="bd-apps">
<span class="bd-app" style="--hue:250"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg></span>
<span class="bd-app" style="--hue:150"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
<span class="bd-app" style="--hue:30"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>
<span class="bd-app" style="--hue:320"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg></span>
<span class="bd-app" style="--hue:200"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/></svg></span>
<span class="bd-app" style="--hue:60"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 10h-1.3A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/></svg></span>
<span class="bd-app" style="--hue:280"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg></span>
<span class="bd-app" style="--hue:0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg></span>
<span class="bd-app" style="--hue:170"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
<span class="bd-app" style="--hue:100"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 2v20M2 12h20"/></svg></span>
<span class="bd-app" style="--hue:230"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg></span>
<span class="bd-app" style="--hue:350"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 0 0-7.8 7.8L12 21l8.8-8.6a5.5 5.5 0 0 0 0-7.8z"/></svg></span>
<span class="bd-app" style="--hue:130"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="m22 2-7 20-4-9-9-4z"/></svg></span>
<span class="bd-app" style="--hue:40"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg></span>
<span class="bd-app" style="--hue:300"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4h16v12H5.2L4 17.2z"/></svg></span>
<span class="bd-app" style="--hue:210"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"/></svg></span>
<span class="bd-app" style="--hue:80"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M20 7H4v13h16zM16 7V4H8v3"/></svg></span>
<span class="bd-app" style="--hue:270"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>
</div>
</div>
<div class="mv-bento-body">
<svg class="mv-bento-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="M17.5 14v7M14 17.5h7"/></svg>
<h3 class="mv-bento-title">120+ integrations</h3>
<p class="mv-bento-description">Chat, storage, calendar, CRM: connect your tools in two clicks, no code.</p>
</div>
<a class="mv-bento-cta" href="#bento-demo">Browse the catalog <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</article>
<article class="mv-bento-card">
<div class="mv-bento-visual" aria-hidden="true" style="--mv-bento-visual-height: 100%">
<div class="bd-shield"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5.3 3.4 9.9 8 11 4.6-1.1 8-5.7 8-11V5z" opacity=".9"/><path d="m8.5 12 2.5 2.5 4.5-5" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</div>
<div class="mv-bento-body">
<svg class="mv-bento-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<h3 class="mv-bento-title">Security</h3>
<p class="mv-bento-description">End-to-end encryption, hosted in the EU.</p>
</div>
<a class="mv-bento-cta" href="#bento-demo">Read the white paper <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</article>
</div>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
data-span | 1x1 | 2x1 | 3x1 | 4x1 | 1x2 | 2x2 | 3x2 | 1x3 | 1x1 | Columns × rows spanned by the card (on .mv-bento-card). |
data-columns | 2 | 3 | 4 | 3 | Number of columns (on .mv-bento). |
data-variant | accent | muted | Tinted or muted background (on .mv-bento-card). |
CSS classes
| Name | Description |
|---|---|
mv-bento | Grid (12 sub-columns, 3 visible columns by default). Query container named mv-bento. |
mv-bento-card | Card; children: -visual, -body, -cta. |
mv-bento-visual | Decorative background visual (aria-hidden), fading toward the bottom, slight zoom on hover. |
mv-bento-body | Text block: -icon, -title, -description. Slides up on hover to make room for the CTA. |
mv-bento-cta | Action link, a direct child of the card: appears on hover/focus and makes the whole card clickable. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-bento-row | 17rem | Row height. |
--mv-bento-gap | var(--mv-space-4) | Gap. |
--mv-bento-radius | var(--mv-radius-2xl) | Card radius. |
--mv-bento-padding | var(--mv-space-5) | Card padding. |
--mv-bento-visual-height | 70% | Height of the visual area. |
Accessibility
A single link per card (.mv-bento-cta) stretched over the whole surface: one tab stop, an explicit accessible name. The CTA is also revealed on keyboard focus (:focus-within) and stays visible on touch screens (hover: none). Visuals are decorative (aria-hidden).