|
| 1 | +--- |
| 2 | +title: "Masonry Grid Goes CSS-Only: An Experimental Approach" |
| 3 | +description: A new experimental approach to masonry layouts using CSS Grid's grid-row span property. Pure CSS, perfect for SSR, but with trade-offs. |
| 4 | +sidebar: |
| 5 | + hidden: true |
| 6 | +head: |
| 7 | + - tag: meta |
| 8 | + attrs: |
| 9 | + property: og:image |
| 10 | + content: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frc1qxx2g8gzaaw3iv5m.png |
| 11 | + - tag: meta |
| 12 | + attrs: |
| 13 | + name: twitter:image:src |
| 14 | + content: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frc1qxx2g8gzaaw3iv5m.png |
| 15 | +--- |
| 16 | + |
| 17 | +I recently published an [article](https://masonry-grid.js.org/articles/masonry-grid-a-14-kb-library-that-actually-works/) about Masonry Grid, and here I am again with another post. What happened? |
| 18 | + |
| 19 | +Well, something interesting popped up in the Reddit comments. |
| 20 | + |
| 21 | +## A CSS Trick from the Comments |
| 22 | + |
| 23 | +After sharing the initial release, [Zardoz84](https://www.reddit.com/user/Zardoz84/) in the comments linked to [a clever CSS-only masonry example](https://codepen.io/zardoz89/pen/KKVEGbw). The example used a neat trick with `grid-row: span X` to create a masonry effect without any JavaScript. |
| 24 | + |
| 25 | +The original example was hand-tuned for specific content, but the technique itself caught my attention. Could this approach work with dynamic content? |
| 26 | + |
| 27 | +## Adapting the Span Trick |
| 28 | + |
| 29 | +The core idea is simple: |
| 30 | + |
| 31 | +```css |
| 32 | +.grid { |
| 33 | + display: grid; |
| 34 | + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| 35 | +} |
| 36 | + |
| 37 | +.item { |
| 38 | + aspect-ratio: var(--width) / var(--height); |
| 39 | + grid-row: span calc(var(--height) / var(--width) * var(--precision)); |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +The trick is elegant: each item maintains its aspect ratio with CSS `aspect-ratio`, then spans multiple grid rows based on that ratio. The `precision` parameter acts as a multiplier - the higher the value, the more accurately the aspect ratio is maintained. |
| 44 | + |
| 45 | +I adapted this technique to work with dynamic content and aspect ratios, and that's how `SpannedMasonryGrid` was born: |
| 46 | + |
| 47 | +```tsx |
| 48 | +import { SpannedMasonryGrid as MasonryGrid, SpannedFrame as Frame } from '@masonry-grid/react' |
| 49 | + |
| 50 | +<MasonryGrid |
| 51 | + frameWidth={200} |
| 52 | + gap={10} |
| 53 | + precision={10} // Controls aspect-ratio accuracy |
| 54 | +> |
| 55 | + <Frame width={16} height={9}> |
| 56 | + <img src="photo1.jpg" /> |
| 57 | + </Frame> |
| 58 | + <Frame width={3} height={2}> |
| 59 | + <img src="photo2.jpg" /> |
| 60 | + </Frame> |
| 61 | + {/* More frames... */} |
| 62 | +</MasonryGrid> |
| 63 | +``` |
| 64 | + |
| 65 | +## The Obvious Advantages |
| 66 | + |
| 67 | +**No JavaScript Required** |
| 68 | + |
| 69 | +The layout is pure CSS. Once the HTML is rendered, the browser handles everything. No observers, no reflow calculations, no JavaScript at all. |
| 70 | + |
| 71 | +**Perfect SSR Compatibility** |
| 72 | + |
| 73 | +Since the layout is determined entirely by CSS, there's no client-side recalculation after hydration. What the server renders is what the user sees - no layout shifts, no flicker. |
| 74 | + |
| 75 | +## The Trade-offs |
| 76 | + |
| 77 | +But it's not all perfect. There are some notable limitations: |
| 78 | + |
| 79 | +**Aspect Ratio Imprecision** |
| 80 | + |
| 81 | +Due to how `grid-row: span` works with fractional values, frames can't maintain their aspect ratios with 100% accuracy. |
| 82 | + |
| 83 | +The `precision` parameter (default: 10) lets you control this: |
| 84 | + |
| 85 | +- **Lower precision (~10)**: Less accurate aspect ratios, but very stable |
| 86 | +- **Higher precision (>=100)**: More accurate sizes, but potential for visual bugs in some browsers with many items |
| 87 | + |
| 88 | +**Gap Workaround Required** |
| 89 | + |
| 90 | +CSS Grid's native `gap` property can't be used with this technique - it would break the span calculations. Instead, we use negative margins and padding: |
| 91 | + |
| 92 | +```css |
| 93 | +.grid { |
| 94 | + margin: calc(-1 * var(--gap) / 2); |
| 95 | +} |
| 96 | + |
| 97 | +.frame > div { |
| 98 | + inset: calc(var(--gap) / 2); |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +It works, but it's a workaround, not a clean solution. |
| 103 | + |
| 104 | +## Conclusion |
| 105 | + |
| 106 | +This is an **experimental** approach - it has trade-offs, and the classic `BalancedMasonryGrid` and `RegularMasonryGrid` remain the safer options for most cases. But if the CSS-only nature fits your needs, give it a try! |
| 107 | + |
| 108 | +Check out the interactive examples: |
| 109 | + |
| 110 | +- [React](https://masonry-grid.js.org/examples/react-spanned/) |
| 111 | +- [Preact](https://masonry-grid.js.org/examples/preact-spanned/) |
| 112 | +- [Svelte](https://masonry-grid.js.org/examples/svelte-spanned/) |
| 113 | +- [SolidJS](https://masonry-grid.js.org/examples/solid-spanned/) |
| 114 | + |
| 115 | +Play with the precision slider and see how it affects the layout. You'll quickly get a feel for the trade-offs. |
| 116 | + |
| 117 | +The library is open source and available on [GitHub](https://github.com/TrigenSoftware/masonry-grid). Contributions, feedback, and bug reports are always welcome! |
0 commit comments