Ibadehin Mojeed I'm an advocate of project-based learning. I also write technical content around web development.

How to create a responsive image gallery with CSS flexbox

10 min read 3016

How To Create A Responsive Image Gallery With Flexbox

Galleries provide an effective way to showcase a collection of high-quality images. In web projects, developers create image galleries to display images in a grid-like layout, making it easier for users to browse them.

There are several ways to create this type of layout. In this tutorial, we will cover how to use the CSS Flexible Box Layout Module (flexbox) to create a responsive image gallery that looks amazing on all devices.

We will use three example projects to demonstrate how flexbox lets us create various layouts. The first and second projects are naturally responsive, without using CSS media queries, which is one of the benefits of flexbox. However, the second and third projects provide a more accurate image preview by maintaining image aspect ratios.

At the end of this tutorial, we will understand how flexbox is applied to establish three types of responsive image gallery layouts.

To follow this tutorial, a basic knowledge of HTML and CSS is required.

Jump ahead:

CSS flexbox overview

flexbox is a model designed for creating layouts in one dimension (i.e., rows or columns) at a time. It provides access to properties that allow you to align and justify flex items inside flex containers.

In addition, flexbox can wrap items onto multiple lines to achieve a grid-like structure, as seen in the example projects below.

When flexbox wraps items, it treats every line as a separate flex line in the flex container. Thus, it justifies these items based on their size and the available space on that flex line. In the next section, we will get started with the usage.

The first project uses a simple layout, as seen below:

Flexbox Project 1 Showing Nine Images Of Equal Dimensions In A Grid Layout

This gallery layout is ideal for uniform image dimensions.

To create this first flexbox project, let’s create an HTML file and add the following markup:

<div class="container">
  <!-- heading text -->
  <ul class="image-gallery"<li>
      <img src="https://source.unsplash.com/VWcPlbHglYc/640x416" alt="" />
      <div class="overlay"><span>Image title</span></div>
    </li>
    <!-- other items here -->
  </ul>
</div>

We used a ul element to group a collection of images located in the li. We can also use another element, such as div. For brevity, the code block above only shows one item within the ul container. See the complete markup on CodeSandbox.

Every li element contains an img element along with a div element that will show an overlay whenever we hover over an image. For this project, we are using free images and appending equal dimensions to the source URL to get images of the same size.

Presently, no styles are applied, so the images will default to stacking on top of each other.

Now, we can introduce flexbox to lay out our images. The focus is on the ulwrapper element holding all the image items.



First, we must make the ul wrapper a flex container by setting its display property to flex. Once we do this, its direct children (the li elements) become flex items.

.image-gallery {
  display: flex;
}

Now, all the flex items immediately line up on the main axis. By default, the main axis is the row dimension. This behavior is the result of flexbox being a one-dimensional layout model.

Presently, our images overflow the viewport because they cannot fit in. We will address this by updating the styles applied to the flex container and also applying styles to the flex items. Let’s update the CSS, so we have the following:

.image-gallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.image-gallery > li {
  flex-basis: 350px; /* width: 350px; */
}

.image-gallery li img {
  object-fit: cover;
  max-width: 100%;
  height: auto;
  vertical-align: middle;
  border-radius: 5px;
}

Here’s a closer look at the styles being applied in the flex container:

  • The flex-wrap property ensures that flex items will wrap onto another line
  • The justify-content property with a value of center will center items on the main axis
  • The gap property sets a gap between rows and columns (in this case, 10px)

On the flex item, we applied a flex-basis: 350px;. Every flex item can grow or shrink from its natural size. By default, they have the following declarations: flex-grow: 0, flex-shrink: 1, and flex-basis: auto. Meaning, flex items use their natural content size if no size is defined. In our case, we defined a size of 350px. So, individual items take a width value of 350px. With the default grow and shrink value, the items can shrink from their flex-basis to fit the container but not grow from it. The second project below will discuss how to make flex items grow from their initial length.

The object-fit: cover; we applied to the image keeps its aspect ratio and ensures it clips to fit. Finally, adding max-width: 100%; and height: auto; ensures images are responsive and can scale well. We can also use width: 100%; and height: 100%; instead. In the end, the layout looks like so:

Last Row Of Flexbox Project 1 Without Image Alignment

Aligning the last row

As seen above, the items in the last row do not align with the previous row. This is a common problem with flexbox.

We mentioned earlier that when flex items wrap, every line is a new flex line and items are justified based on the available space in that flex line. In this case, items in the last row are centered because we applied justify-content: center; to the flex container.

A quick fix is to use ::after to create a pseudo-element in the flex container. Then, we can set the size of the flex-basis CSS property as equal to the size of the flex items.

.image-gallery::after {
  content: "";
  flex-basis: 350px;
}

This approach works well. An alternate approach is to use CSS grid, a two-dimensional layout system.

Displaying overlay on hover

Let’s say we want to display some overlay text whenever users hover over the images. To achieve this, start by updating the li to include the following style declarations:

.image-gallery > li {
  /* ... */
  position: relative;
  cursor: pointer;
}

Then target the div‘s overlay class in the li and apply the following style rules:

.overlay {
  position: absolute;
  width: 100%;
  height: 100%;
  background: rgba(57, 57, 57, 0.502);
  top: 0;
  left: 0;
  transform: scale(0);
  transition: all 0.2s 0.1s ease-in-out;
  color: #fff;
  border-radius: 5px;
  /* center overlay text */
  display: flex;
  align-items: center;
  justify-content: center;
}

/* hover */
.image-gallery li:hover .overlay {
  transform: scale(1);
}

Now, let’s save and test our project to ensure it works as expected.

Handling flexbox image overflowing

A common problem that beginners implementing flex properties witness is seeing images overflowing their flex container. It is imperative to know that images will overflow either in the horizontal axis or vertical axis if the container’s available size (i.e., its width or height) cannot contain the flex item(s).

Earlier, we resolved the horizontal overflow by adding a max-width: 100%; to the img to ensure images are responsive and contained within their li container. Remember, the lis being flex items have a default flex-shrink: 1, so their containing images can shrink to fit any reduced space.

On the vertical overflow, we must avoid adding a fixed height like 100vh on the flex container, so it doesn’t become smaller to contain flex items:

.image-gallery {
  /* ... */
  /* don't add a fixed height */
  height: 100vh;
}

To see this in practice, also add a section after the ul flex container element:

<div class="container">
  <!-- heading -->
  <ul class="image-gallery">
    <!-- ... -->
  </ul>

  <section>
    <h2>This another section</h2>
  </section>
</div>

In this CodeSandbox, we should see that the section heading text lives behind the images gallery due to overflow. That’s all for the first project. See the demo here and the complete code on CodeSandbox.

In the second and third example projects, we will create layouts that will maintain the images’ aspect ratios.

The second project’s layout will maintain the images’ aspect ratios, as shown below:

Flexbox Project 2 Showing Nine Images With Different Aspect Ratios In A Grid Layout

The markup for the second project is mostly the same as that for the first project. The difference is that we will not append fixed dimensions to the image URLs:

<div class="container">
  <!-- heading text -->
  <ul class="image-gallery">
    <li>
      <img src="https://source.unsplash.com/VWcPlbHglYc" alt="" />
      <div class="overlay"><span>Image title</span></div>
    </li>
    <!-- other items here -->
  </ul>
</div>

Next, we introduce flexbox and alignment rules by adding the following code to our CSS file:

.image-gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.image-gallery > li {
  height: 300px;
  cursor: pointer;
  position: relative;
}

.image-gallery li img {
  object-fit: cover;
  width: 100%;
  height: 100%;
  vertical-align: middle;
  border-radius: 5px;
}

Now, let’s compare the above rules to the first project.

First, instead of a defined width on the li flex items, we have a defined height. Also, notice we are not aligning the items with the justify-content property, and on the img, we defined a width and height value of 100%. With this, the layout looks like this:

Images In Flexbox Project 2 Without Justification, Leaving White Space At Top Right

Using the flex-grow flexbox property

The flex-grow lets us proportionally distribute the images by making the flex items grow from their initial length and fill the available space. It lets us specify how much an item should grow relative to other flex items.

By assigning an equal positive value to the flex items, the remaining space will be distributed equally between them. Let’s update our code to include flex-grow: 1; like so:

.image-gallery > li {
  flex-grow: 1;
  /* ... */
}

As mentioned earlier, default flex items have the following properties values: flex-grow: 0, flex-shrink: 1, and flex-basis: auto. The shorthand equivalent looks like this:

flex: 0 1 auto;

To use the shorthand in our code, we will have the following:

.image-gallery > li {
  flex: 1 1 auto; /* or flex: auto; */
  /* ... */
}

The value auto in the declaration matches the flex-basis. Thus, making flex items use their natural content size (in this case, image size). They can now grow and also can shrink if necessary.

Now that the flex items have filled the available space, we have again encountered the last row alignment issue with flexbox. In this case, the two items in the last row will grow to fill space equivalent to three columns.

To remedy this behavior, we will use the ::after pseudo-element once again on the flex container, like so:

.image-gallery::after {
  content: "";
  flex-grow: 999;
}

We can play with the flex-grow value to determine an output that suits our layout. Now, let’s save our files and ensure it works as expected. This brings us to the end of the second project; see the demo here and the complete code here.

Maintaining image aspect ratios in a three-column layout

In the second project, we learned how to create a responsive image gallery layout that maintains image aspect ratios without using media queries. However, using flexbox with media queries allows us to achieve a specific layout while still maintaining image aspect ratios.

To demonstrate this, we will create a third responsive image gallery project that will maintain image aspect ratios, but in a three-column layout, like this:

Flexbox Project 3 Showing Images With Different Dimensions Laid Out In Three Columns

This responsive image gallery will display three columns on big screens and one column on small screens. To achieve this, we will create a markup containing three columns of elements, like so:

<div class="container">
  <!-- heading text -->
  <div class="image-gallery">
    <div class="column"&;gt;
      <div class="image-item">
        <img src="https://source.unsplash.com/VWcPlbHglYc" alt="" />
        <div class="overlay"><span>Image title</span></div>
      </div>
      <!-- other items here -->
    </div>
    <div class="column">
      <!-- other items here -->
    </div>
    <div class="column">
      <!-- other items here -->
    </div>
  </div>
</div>

Each of the columns should contain a list of the images we’d like to display. For brevity, the code block above only shows one image. See the complete markup on CodeSandbox.

These columns will serve as a flex container for the image’s items. Likewise, the columns will be flex items within the wrapper element.

For this reason, we will apply the following style rules:

.image-gallery {
  /* Mobile first */
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.image-gallery .column {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.image-item img {
  width: 100%;
  border-radius: 5px;
  height: 100%;
  object-fit: cover;
}

@media only screen and (min-width: 768px) {
  .image-gallery {
    flex-direction: row;
  }
}

Starting with the mobile layout, we ensured all items are displayed as a column. Unlike the earlier projects, we added a flex-direction property to change the default row alignment to be a column instead.

Then, for big screens (min-width: 768px), we changed the direction of the outer flex container to be a row.

Let’s save our files and ensure it works. See the complete code on CodeSandbox.

Browser support for CSS flexbox

CSS flexbox is widely supported by modern browsers and partially supported in older browsers like IE 10 and IE 11. See browser support here.

Browser Support CSS Flexbox

This extensive support makes CSS flexbox a candidate to use in production, as we can also see in the global usage statistics highlighted in the image above.

However, we may still want to provide a fallback for browsers with limited support, like IE 10 and IE 11. This way, we can deploy a progressive enhancement strategy in our development.

Implementing progressive enhancement

Progressive enhancement lets us provides a solution that works for all browsers and then enhances the experience for browsers that support newer features. This strategy ensures that users of older browsers receive a decent experience closer to the latest features. However, this involves adding a bit more CSS code but not doubling the amount of the CSS.

CSS is designed to enhance the experience using this strategy seamlessly. We’ve heard of the cascading principle in which a later declaration in a rule overwrites a previous one. Another behavior is that browsers ignore any CSS property or value they don’t understand. That means older browsers that don’t support CSS flexbox ignore its related declarations and fall back to legacy techniques that we’ll mention below.

Implementing a fallback and workarounds

We can create a layout similar to flexbox for older browsers using CSS fallback methods like display: inline-block declaration or float property.

This approach works because when a floated or inline-block item becomes a child to a flex container, they automatically become flex items, and their initial behavior is ignored. So, in a browser that supports a flexbox layout, we get the benefits of the modern features, and if not, the fallback method is applied, so the users get a closer layout experience.

If we apply a fallback to our first project, our code should look like the following:

/* .... */
.image-gallery {
  /* display: flex; */
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  /* the above flex properties  */

  /* fallback */
  text-align: center;
}

.image-gallery > li {
  flex-basis: 350px; /*width: 350px;*/
  position: relative;
  cursor: pointer;

  /* fallback */
  display: inline-block;
  width: 350px;
  margin: 0 5px 10px 5px;
  /* end fallback */
}
/* .... */

Notice how we removed the display: flex; declaration to simulate browsers that don’t support flexbox in the code. By doing so, the container and the items inside it lose all the benefits of flexbox.

We then applied the display: inline-block; fallback on the individual items to produce a similar layout experience. If we save the file and test our project, we will have a closer layout.

In reality, we mustn’t remove the display: flex; to have a better experience for supported browsers. So, if we return the display flex alongside the fallback, the spaces between items become more extensive due to the margin included as a fallback.

We can deal with this issue by detecting if flexbox is supported and therefore remove the extra space by setting margin: 0; on the flex items. To detect CSS features, we will apply the feature queries.

Testing with feature queries

Feature queries let us test browser support for CSS features (in our case, display: flex) using the @supports rule. The following code will detect if the flex property is supported in a browser and apply the CSS rule within:

@supports (display: flex) {
  .image-gallery > li {
    margin: 0;
  }
}

The @supports don’t change CSS specificity. So we must place it after the style rule to overwrite. In our case, we must place it after the following rule:

.image-gallery > li {
  /* ... */
}

If we save our project, we should now see the expected margin between flex items.

An alternative is to place all the modern CSS features not supported in older browsers in a feature query as follows:

@supports (display: flex) {
  .image-gallery {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
  }

  .image-gallery > li {
    flex-basis: 350px; /*width: 350px;*/
    margin: 0;
  }

  .image-gallery::after {
    content: "";
    flex-basis: 350px;
  }
}

In the CSS code on CodeSandbox, we have placed the fallback code outside the @support block and the flexbox-related code inside the block. Open the project and confirm it works as expected.

Conclusion

CSS flexbox has the potential to wrap, align, and justify items in a container. This makes it handy for creating a responsive layout in a grid-like structure.

In this tutorial, we used flexbox to create three responsive image gallery projects that look amazing on all devices. We also learned how to implement fallback and workarounds for users with older browsers so they also experience a similar layout.

If you have questions or contributions, share your thoughts in the comments section. And if you enjoyed reading this tutorial, endeavor to share it around the web.

Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web and mobile apps — .

Ibadehin Mojeed I'm an advocate of project-based learning. I also write technical content around web development.

2 Replies to “How to create a responsive image gallery with CSS…”

  1. Excellent article! I’m using the pointers here in my own project. I found that since the text over the images is only visible on rollover, they are not visible on a mobile device, so I’m just putting the text over the image and the color overlay so it’s visible at all times.

Leave a Reply