BentoGrid
A grid of tiles spanning columns and rows, with titles, tints and links, packed to leave no holes.
Demo
Fast
Pages in 40 ms, components resumed, not rebuilt.
40 ms
Safe
Escaped by default.
Docs
Guides and the reference.
Small
30 KB of runtime, once.
Tested
98%
Live
Pages follow the data.
The same wall in a 480px-wide box: two columns, wide tiles across both.
Fast
Pages in 40 ms, components resumed, not rebuilt.
40 ms
Safe
Escaped by default.
Docs
Guides and the reference.
Small
30 KB of runtime, once.
Tested
98%
Live
Pages follow the data.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/bento_grid.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/bento_grid.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/bento_grid.qwe
view BentoGridDemoTiles() = [ <BentoTile cols={2} rows={2} tone="accent" title="Fast" subtitle="Pages in 40 ms, components resumed, not rebuilt." class="bento-grid-demo-big"> <p style="margin: 12px 0 0; font-size: 40px; font-weight: 650; letter-spacing: -.02em">40 ms</p> </BentoTile>, <BentoTile title="Safe" subtitle="Escaped by default." class="bento-grid-demo-small"/>, <BentoTile title="Docs" subtitle="Guides and the reference." href="https://quillolang.org/docs/" class="bento-grid-demo-link"/>, <BentoTile cols={2} tone="subtle" title="Small" subtitle="30 KB of runtime, once." class="bento-grid-demo-wide"/>, <BentoTile tone="success" title="Tested"><p style="margin: 0; font-size: 28px; font-weight: 650">98%</p></BentoTile>, <BentoTile tone="info" title="Live"><p style="margin: 0; color: var(--ui-muted)">Pages follow the data.</p></BentoTile> ]view BentoGridDemo() = <div.demo-stack> <BentoGrid id="bento-grid-demo-wall" columns={4} rows="140px"><BentoGridDemoTiles/></BentoGrid> <p.demo-note>The same wall in a 480px-wide box: two columns, wide tiles across both.</p> <div style="width: 480px; max-width: 100%"> <BentoGrid id="bento-grid-demo-narrow" columns={4} rows="120px" gap="sm"><BentoGridDemoTiles/></BentoGrid> </div> </div>Source
ui/bento_grid.qwe
// BentoGrid: a grid of tiles of different sizes, each spanning columns and rows,// as on a product page's feature wall or a dashboard's front. The tiles are// BentoTiles; a wide one takes two columns on a phone and a narrow one takes one.//// <BentoGrid columns={4} rows="160px">// <BentoTile cols={2} rows={2} title="Fast" subtitle="Pages in 40 ms">...</BentoTile>// <BentoTile title="Safe">...</BentoTile>// <BentoTile cols={2} title="Small" href="/docs/size">...</BentoTile>// </BentoGrid>//// BentoGrid: columns, how many across (4); rows: each row's height, as CSS ("180px");// gap: pixels (16, the default) or "none", "xs" (4), "sm" (8), "md" (16), "lg" (24), "xl" (32).// Narrower than 640px it is two columns, and narrower than 400px one; tiles are// packed to leave no holes. The grid measures its own width, not the window's.// BentoTile: cols, rows: how many it spans (1 each). title, subtitle: its heading// and the line under it, at the top; children: the rest. href makes the tile a link.// tone: "plain" (default), "subtle", "accent", "success", "warning", "danger" or// "info", a tint. image: a URL shown across the whole tile, behind the text.import ui/theme.qwe use ui_clslet bento_grid_css = css`.ui-bento-wrap { container-type: inline-size; min-width: 0; }.ui-bento { display: grid; grid-template-columns: repeat(var(--ui-bento-cols, 4), minmax(0, 1fr)); grid-auto-rows: var(--ui-bento-row, 180px); grid-auto-flow: dense; gap: var(--ui-bento-gap, 16px); min-width: 0; color: var(--ui-fg); font: 14px/1.5 var(--ui-font);}.ui-bento-tile { position: relative; display: flex; flex-direction: column; grid-column: span var(--ui-bento-c, 1); grid-row: span var(--ui-bento-r, 1); min-width: 0; overflow: hidden; padding: 18px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: inherit; text-decoration: none;}.ui-bento-head { display: grid; gap: 2px; margin-bottom: 8px; }.ui-bento-title { margin: 0; font-size: 16px; font-weight: 600; line-height: 1.3; }.ui-bento-sub { margin: 0; color: var(--ui-muted); }.ui-bento-body { flex: 1; min-height: 0; }.ui-bento-body > :first-child { margin-top: 0; }.ui-bento-body > :last-child { margin-bottom: 0; }.ui-bento-image { position: absolute; inset: 0; z-index: -1; background: center / cover no-repeat; }.ui-bento-image::after { content: ""; position: absolute; inset: 0; background: linear-gradient(transparent 40%, color-mix(in srgb, var(--ui-surface) 90%, transparent)); }.ui-bento-tile:has(.ui-bento-image) { isolation: isolate; justify-content: flex-end; }.ui-bento-subtle { background: var(--ui-subtle); border-color: transparent; }.ui-bento-accent { background: var(--ui-accent-soft); border-color: transparent; }.ui-bento-accent .ui-bento-title { color: var(--ui-accent); }.ui-bento-success { background: var(--ui-success-soft); border-color: transparent; }.ui-bento-success .ui-bento-title { color: var(--ui-success); }.ui-bento-warning { background: var(--ui-warning-soft); border-color: transparent; }.ui-bento-warning .ui-bento-title { color: var(--ui-warning); }.ui-bento-danger { background: var(--ui-danger-soft); border-color: transparent; }.ui-bento-danger .ui-bento-title { color: var(--ui-danger); }.ui-bento-info { background: var(--ui-info-soft); border-color: transparent; }.ui-bento-info .ui-bento-title { color: var(--ui-info); }a.ui-bento-tile { transition: border-color .15s, box-shadow .15s; }a.ui-bento-tile:hover { border-color: var(--ui-strong); box-shadow: var(--ui-shadow); }a.ui-bento-tile:focus-visible { outline: none; box-shadow: var(--ui-focus); }@container (max-width: 639px) { .ui-bento { grid-template-columns: repeat(2, minmax(0, 1fr)); } .ui-bento-tile { grid-column: span 1; } .ui-bento-wide { grid-column: span 2; }}@container (max-width: 399px) { .ui-bento { grid-template-columns: minmax(0, 1fr); } .ui-bento-tile, .ui-bento-wide { grid-column: span 1; }}@media (prefers-reduced-motion: reduce) { a.ui-bento-tile { transition: none; } }`fn bento_grid_gap(gap) -> str = match gap { int as n => `${n}px` float as n => `${n}px` "none" => "0" "xs" => "4px" "sm" => "8px" "lg" => "24px" "xl" => "32px" _ => "16px" }view BentoGrid(columns: int = 4, rows: str = "180px", gap = 16, id: str? = null, class: str? = null, children) { style global bento_grid_css <div class={ui_cls(["ui-bento-wrap", class])} id={id}> <div.ui-bento style={`--ui-bento-cols: ${columns}; --ui-bento-row: ${rows}; --ui-bento-gap: ${bento_grid_gap(gap)}`}>{children}</div> </div>}view BentoTile(cols: int = 1, rows: int = 1, title: str? = null, subtitle: str? = null, href: str? = null, tone: str = "plain", image: str? = null, id: str? = null, class: str? = null, children) { style global bento_grid_css let cls = ui_cls(["ui-bento-tile", cols > 1 && "ui-bento-wide", tone != "plain" && `ui-bento-${tone}`, class]) let st = `--ui-bento-c: ${cols}; --ui-bento-r: ${rows}` let body = [ image != null && <div.ui-bento-image style={`background-image: url("${replace(image, "\"", "%22")}")`} aria-hidden="true"></div>, (title != null || subtitle != null) && <div.ui-bento-head> {title != null && <h3.ui-bento-title>{title}</h3>} {subtitle != null && <p.ui-bento-sub>{subtitle}</p>} </div>, <div.ui-bento-body>{children}</div> ] href != null ? <a class={cls} id={id} style={st} href={href}>{body}</a> : <div class={cls} id={id} style={st}>{body}</div>}Related
MasonryCards of different heights packed into columns, each filled from the top, on CSS columns alone.6 views · 7 downloadsBrowserFrameA mock browser window around its content, with the address in its bar, for screenshots and demos.7 views · 7 downloadsCardA surface for one thing, with a title, a header and a footer, or a link as a whole.8 views · 6 downloadsColumnsChildren side by side, equal or weighted, that go one under another when their box is narrow.7 views · 9 downloadsCoverA block as tall as the screen with one thing in its middle, and something at its top and bottom if given.7 views · 7 downloadsGridColumns that fit the screen: as many as fit a minimum width, or a fixed number that folds on phones.6 views · 8 downloads
Thread
Thread 0 posts
On BentoGrid: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.