This post was inspired by a CSS-Tricks article about different approaches for building masonry layouts. At the end, I would like to present a responsive solution which doesn’t rely on flexbox, multi-column or grid layout, but works even in browsers without JavaScript.

I was on my journey building a website for our student society of design, based on wireframes crafted along with my friends. For the section showcasing our illustrations, we were about to go with a full-width masonry layout of images.

A masonry layout based on a demo of React Photo Gallery, images being courtesy of Unsplash

At first thought, React Photo Gallery provided a simple drop-in solution, but it had several drawbacks:

🐌 Sluggish JavaScript-based calculations when resizing the viewport

📦 Pulling in an additional dependency with its own styling approach

⛔ Without client-side scripting, no image would show up by default

We were going to build a layout with a dynamic amount of columns based on pre-defined breakpoints:

The mobile and desktop browsing experiences would vary based on viewport width

While it’s possible to pull off breakpoint-based layout transitions with regular media queries, using styled-components with Rebass felt more convenient and systematic.

Creating components with design systems in mind

Defining the interface of a reusable React component is non-trivial, especially when it comes to responsiveness. Media templates let developers declare breakpoints in an application-wide scope, but they can hardly be used for specifying responsive behavior through props. 🤷

Fortunately, Rebass provides a way to address our issue by promoting the use of responsive props. In addition, first-class support for design systems is realizable through themeable props. 🎨

Defining the interface of a responsive masonry layout

In order to control the amount of items per row with breakpoints, the desired API of our component became the following:

The itemsPerRow prop allows us to show 2 images per row on smaller screens and 3 on larger displays.

Calculating the size of a single row item

Our gallery consists of rows in which the height of images are equal:

Each row may have a different height, but the height of items in a single row is fixed

Images should be sized in proportion to their aspect ratios. For example, the second box shown on the figure above has an aspect ratio of y and should have a width of y / (x + y + z) relative to 100% (the row’s entire width).

The wider an image is, the more horizontal space it takes to reach the height of its (potentially narrower) neighbors.

As we are serving static images, the aspect ratio of them is known in build time. Gatsby makes it possible to parse structured data from YAML files like the following:

Querying metadata of the referenced images is also possible, through GraphQL. As a bonus, we may also opt in to use gatsby-image for serving our images in the most appropriate sizes available:

Loosening constraints to build the first prototype

At first, we wanted to build a masonry component with multiple screen resolutions in mind. Considering the complexity of our task, the first prototype should only be optimized for devices with smaller displays. 📱

The implementation is based on our calculations outlined earlier:

Using Rebass is not necessary here, I used it only to make the code as similar to the next iteration as possible

Our component is already capable of rendering media acquired through GraphQL. In the code below, the caption of each image is also constructed:

As a result of server-side rendering, all the calculations are made upfront. No JavaScript was executed on the client side to produce the layout below: