UI Components Easy
Auto-fit Bento Tiles
Resize the tile minimum to see CSS Grid auto-fit adapt without a breakpoint list.
Open in Lab
MCP
html css javascript react
Targets: TS JS HTML React
Code
*, *::before, *::after { box-sizing: border-box; }
:root {
--bg: #0b1020;
--panel: #121a2d;
--line: #263555;
--line-2: #2b3d62;
--text: #eef2ff;
--muted: #a8b5d1;
--accent: #78a9ff;
--accent-2: #c084fc;
color-scheme: dark;
}
body {
margin: 0;
min-height: 100vh;
padding: clamp(1rem, 4vw, 3rem);
background:
radial-gradient(900px 500px at 12% -10%, #1b2440 0%, transparent 60%),
var(--bg);
color: var(--text);
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}
button, input, textarea, select { font: inherit; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .86em; }
.muted { color: var(--muted); }
.demo {
width: min(980px, 100%);
margin: auto;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 22px;
padding: clamp(1rem, 3vw, 2rem);
box-shadow: 0 20px 70px #0004;
}
.head h1 { margin: 0 0 .35rem; font-size: 1.5rem; letter-spacing: -.02em; }
.head p { margin: 0; }
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem 1.25rem;
margin: 1.4rem 0 .9rem;
padding: 1rem;
background: #18233b;
border: 1px solid var(--line-2);
border-radius: 14px;
}
.ctl { display: grid; grid-template-columns: 1fr auto; gap: .3rem .6rem; align-items: center; }
.ctl label { font-size: .8rem; color: var(--muted); letter-spacing: .02em; }
.ctl output { font: 600 .8rem ui-monospace, Menlo, monospace; color: var(--accent); }
.ctl input[type="range"] { grid-column: 1 / -1; width: 100%; accent-color: var(--accent); }
.ctl.check {
grid-template-columns: auto 1fr;
align-content: center;
font-size: .85rem;
cursor: pointer;
}
.ctl.check input { width: 16px; height: 16px; accent-color: var(--accent-2); }
.readout {
display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
margin: 0 0 1.1rem; color: var(--muted);
}
.pill {
padding: .18rem .6rem;
border-radius: 99px;
background: #c084fc22;
border: 1px solid #c084fc59;
color: var(--accent-2);
font-size: .75rem;
font-weight: 600;
}
.stage {
border: 1px dashed var(--line-2);
border-radius: 16px;
padding: .9rem;
overflow: hidden;
}
.bento {
list-style: none;
margin: 0 auto;
padding: 0;
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 14px;
transition: width .15s ease;
}
.tile {
min-height: 96px;
padding: .9rem;
border-radius: 14px;
border: 1px solid var(--line-2);
background: linear-gradient(160deg, #1b2440, #141c30);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: .4rem;
overflow: hidden;
}
.tile.wide { grid-column: span 2; }
.tile.tall { min-height: 150px; }
.tile b { font-size: .95rem; letter-spacing: -.01em; }
.tile small { color: var(--muted); font-size: .76rem; }
.tile .bar {
height: 6px;
border-radius: 99px;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
transition: width .25s ease;
}
@media (max-width: 600px) {
.demo { border-radius: 16px; }
.tile.wide { grid-column: auto; }
}
@media (prefers-reduced-motion: reduce) {
.bento, .tile .bar { transition: none; }
}(function () {
"use strict";
var grid = document.getElementById("bento");
var minEl = document.getElementById("min");
var gapEl = document.getElementById("gap");
var widthEl = document.getElementById("width");
var fillEl = document.getElementById("fill");
var minOut = document.getElementById("minOut");
var gapOut = document.getElementById("gapOut");
var widthOut = document.getElementById("widthOut");
var ruleOut = document.getElementById("rule");
var colsOut = document.getElementById("cols");
var DATA = [
{ t: "Revenue", s: "$48.2k this week", w: 0.78, wide: true },
{ t: "Active users", s: "1,204 online", w: 0.55 },
{ t: "Latency p95", s: "182 ms", w: 0.31 },
{ t: "Error rate", s: "0.42%", w: 0.12, tall: true },
{ t: "Conversion", s: "3.9% checkout", w: 0.44 },
{ t: "Queue depth", s: "17 jobs", w: 0.26 },
{ t: "Storage", s: "612 GB / 1 TB", w: 0.61, wide: true },
{ t: "Uptime", s: "99.98% (30d)", w: 0.94 },
{ t: "Deploys", s: "12 today", w: 0.5 },
{ t: "Cache hit", s: "88.1%", w: 0.88 }
];
DATA.forEach(function (d) {
var li = document.createElement("li");
li.className = "tile" + (d.wide ? " wide" : "") + (d.tall ? " tall" : "");
var b = document.createElement("b");
b.textContent = d.t;
var s = document.createElement("small");
s.textContent = d.s;
var bar = document.createElement("div");
bar.className = "bar";
bar.style.width = Math.round(d.w * 100) + "%";
li.appendChild(b);
li.appendChild(s);
li.appendChild(bar);
grid.appendChild(li);
});
// Read back the tracks the browser actually resolved for the current rule.
function countColumns() {
var tracks = getComputedStyle(grid).gridTemplateColumns;
if (!tracks || tracks === "none") return 1;
return tracks.split(/\s+/).filter(Boolean).length;
}
function report() {
var n = countColumns();
colsOut.textContent = n + (n === 1 ? " column" : " columns");
}
function apply() {
var min = Number(minEl.value);
var gap = Number(gapEl.value);
var width = Number(widthEl.value);
var mode = fillEl.checked ? "auto-fill" : "auto-fit";
var rule = "repeat(" + mode + ", minmax(" + min + "px, 1fr))";
grid.style.gridTemplateColumns = rule;
grid.style.gap = gap + "px";
grid.style.width = width + "%";
minOut.textContent = min + "px";
gapOut.textContent = gap + "px";
widthOut.textContent = width + "%";
ruleOut.textContent = "grid-template-columns: " + rule;
requestAnimationFrame(report);
}
[minEl, gapEl, widthEl, fillEl].forEach(function (el) {
el.addEventListener("input", apply);
el.addEventListener("change", apply);
});
// Container-driven, not viewport-driven.
if (typeof ResizeObserver === "function") {
new ResizeObserver(report).observe(grid);
} else {
window.addEventListener("resize", report);
}
apply();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Auto-fit Bento Tiles</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="demo" data-demo="Auto-fit Bento Tiles">
<header class="head">
<h1>Auto-fit Bento Tiles</h1>
<p class="muted">One <code>repeat(auto-fit, minmax(<min>, 1fr))</code> rule — no breakpoint list.</p>
</header>
<form class="controls" aria-label="Grid controls">
<div class="ctl">
<label for="min">Tile minimum</label>
<output id="minOut" for="min">180px</output>
<input id="min" type="range" min="80" max="360" step="4" value="180" />
</div>
<div class="ctl">
<label for="gap">Gap</label>
<output id="gapOut" for="gap">14px</output>
<input id="gap" type="range" min="0" max="32" step="1" value="14" />
</div>
<div class="ctl">
<label for="width">Container width</label>
<output id="widthOut" for="width">100%</output>
<input id="width" type="range" min="40" max="100" step="1" value="100" />
</div>
<label class="ctl check">
<input id="fill" type="checkbox" />
<span>Use <code>auto-fill</code> (keep empty tracks)</span>
</label>
</form>
<p class="readout" aria-live="polite">
<code id="rule">grid-template-columns: repeat(auto-fit, minmax(180px, 1fr))</code>
<span id="cols" class="pill">1 column</span>
</p>
<div class="stage">
<ul class="bento" id="bento" aria-label="Bento tiles"></ul>
</div>
</main>
<script src="script.js"></script>
</body>
</html>import { useState } from "react";
export function BentoAutoFitTiles() {
const [items, setItems] = useState(["Alpha", "Beta", "Gamma"]);
return (
<section className="demo">
<h2>Auto-fit Bento Tiles</h2>
<p>Vanilla behavior maps cleanly to Pointer Events and DOM state.</p>
<div className="stack">
{items.map((item, index) => (
<button
className="item"
key={item}
onClick={() => setItems([...items.slice(0, index), ...items.slice(index + 1), item])}
>
{item}
</button>
))}
</div>
</section>
);
}Auto-fit Bento Tiles
Resize the tile minimum to see CSS Grid auto-fit adapt without a breakpoint list.
Support notes
CSS Grid and columns do most of the layout work. The drag demo adds native DnD only for rearrangement; keep tile order in application state when integrating it.
Included demo
- Vanilla HTML, CSS, and JavaScript with zero external dependencies.
- React equivalent using the same interaction model.
- A Lab route for trying the behavior in isolation.
Integration checklist
Keep state updates separate from presentation, preserve semantic labels, and add persistence or server callbacks at the boundary where your product needs them.