No Time Dad

A blog about web development written by a busy dad

Building a Responsive Grid With Auto-fit and No Media Queries

Building on my last post about responsive grids using auto-fit, I thought it’d be a good idea to actually build something useful using auto-fit. My goal is to create a half decent element that’s responsive without media query usage. It’ll use css grid with auto-fit to accomplish this.

What I’m building is a grid comprised of squares where their contents alternate between an image and some text. This kind of element is common for web application landing pages. They’re typically trying to tell the user how awesome the product is and why they should use it.

Lately, my preference is to take a “mobile first” approach to my designs. This term is becoming a little buzzwordy in my opinion, but I have to say that dealing with the small screen layout first somehow does make things easier later on. This responsive grid will be a single column with six rows on small screens making it mobile friendly, and change to two columns with three rows for larger screens.

The tricky part will be making sure that auto-fit doesn’t go wild and start adding more columns. I want it to at most have two columns on the largest of screens. Using auto-fit or even auto-fill is the key to making a css grid responsive, but they can sometimes take on a mind of their own if you’re not careful.

Building the grid

The entire responsive grid squares element is going to consist of a parent div that acts as a wrapper, and a child div that contains the grid and grid items. The reason for the outer parent wrapper is because I want to be able to control the width of the entire grid and the space around it. This can be done directly on the grid selector, but I find it easier to work with if I have an outer div where I control the spacing.

Wrapper

The wrapper is where I’ll set the max-width and the auto-margins. The entire purpose of this is to center the grid container on the page and to add white space on both sides of it.

But, there won’t always be white space on either side, depending on the screen size. Here I’ve set the max-width value to 950px, so any screen size that has a width less than that won’t have any white space on the sides. Which is fine.

On small screens I inherently don’t have a lot of real estate to work with when it comes to the width, so I try not to add too much space on the sides. Otherwise, the content can feel squished.

<div class="wrapper">
  ...
</div>
.wrapper {
  /* Larger max width prevents smaller screens from having white space */
  max-width: 950px;
  /* Use auto-margins to center the grid on the page */
  margin-left: auto;
  margin-right: auto;
}
Grid

The grid selector consists of two lines. Which is great news. The less css to maintain the better. But, as I mentioned in my previous post, the value for the grid-template-columns property isn’t easy to understand at a glance.

The important points below are that I’m telling the grid to create as many columns as will fit given the screen size via repeat(auto-fit ...), then using the minmax function to specificy the column widths.

The minmax function is taking min(100%, 375px) as the first argument, which ensure that the squares only take up the available space on small screens (e.g. don’t show a horizontal scroll bar), or 375px if needed. If those conditions aren’t met, the squares can take up all of the available space via the 1fr (fractal unit) argument.

This is where the max-width I set previously is important. If I didn’t set that value then the grid would take up as much width as it could. This is how I keep the grid to two columns.

<div class="wrapper">
  <div class="grid">
    ...
  </div>
</div>
...
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 375px), 1fr));
}

Below is an example of this would look. Making your browser window smaller should switch the grid to a single column. If you made it larger the grid should switch back to a two by three grid and stay that way. No media queries to be found.

1
2
3
4
5
6

Here is the above demo as a simple React component.

const DemoSquares= () => (
  <div style={{maxWidth: '550px', marginLeft: 'auto', marginRight: 'auto'}}>
    <div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 200px), 1fr))'}}>
      <div style={{border: '1px solid blue', height: '200px'}}>1</div>
      <div style={{border: '1px solid blue', height: '200px'}}>2</div>
      <div style={{border: '1px solid blue', height: '200px'}}>3</div>
      <div style={{border: '1px solid blue', height: '200px'}}>4</div>
      <div style={{border: '1px solid blue', height: '200px'}}>5</div>
      <div style={{border: '1px solid blue', height: '200px'}}>6</div>
    </div>
  </div>
);

Building the squares

Now that I have the responsive grid working, I can customize the items in the grid. In this case, the squares. On small screens, each grid item should alternate between a square with an image and a square with text. Or larger screens, it should be more like a checkerboard pattern with an image and text square each next to each other then reversing on the next row.

Text
Text
Text
Image square

The image square is not as complicated as the text square. The important thing is to pick images that fit well in the square. That perfect photo of the ocean sunset might be stretched and distored in the square. So, it’s best to pick an image that’s close in size to dimensions of the square on various size screens.

// index.jsx
import React from 'react';
import * as styles from './index.module.css';
import fern from './fern.jpg';+-

const Index = () => (
  <div className={styles.wrapper}>
    <div className={styles.grid}>
      ...
      <div className={styles.image_square}>
        <img className={styles.image} src={fern} alt="fern" />
      </div>
      ...
    </div>
  </div>
);

export default Index;
/* index.module.css */
...
.image {
  height: 350px;
  width: 100%;
}
...

There isn’t much to the image selector. It’s height needs to match the match the height of the square next to it or above it (the text square), and it’s width needs to be 100%. The width is important because I want to be sure that the image always takes up the full width of square no matter what, because the square’s width will change depending on the screen size.

Another interesting point is that I added an image_square selector to the img element’s parent div, but there isn’t any definition for image_square in the css file. This is personal preference, and maybe frowned upon, but I’m eventually going to add a text_square div and having the unused selector as a label of sorts makes it easier to read at a glance.

Text square

The last element to deal with in this grid is the text square. Which has a parent div and two child sibling divs. One child for large title text and one child for smaller subtitle text. They’ve each been given selector names that correspond to their purpose.

...
const Index = () => (
  <div className={styles.wrapper}>
    <div className={styles.grid}>
      ...
      <div className={styles.text_square}>
        <div className={styles.title_text}>Awesome title</div>
        <div className={styles.subtitle_text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </div>
      </div>
      ...
    </div>
  </div>
);

export default Index;
/* index.module.css */
...
.text_square {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 350px;
  padding: 1rem;
}

.title_text {
  font-size: 32px;
  font-weight: 600;
}

.subtitle_text {
  opacity: 0.8;
}

Flexbox makes centering things easy. I try to use it for that purpose as much as possible. I don’t have to think about it, it just works. The same is true here for the text_square selector. It’ll center the text vertically and horizontally. I also define the height of the text square, which should match the height of the image square. There are probably more than a few ways to ensure their heights match, and this one is a little redundant but it does the job.

There isn’t anything else too complicated going on in the remaining two selectors. Mostly just small style changes to make the title text and subtitle text stand out from each other. An improvement that I think can be made here is to handle any text wrapping. The spacing might get weird in the square if either text block is long.

Conclusion

I didn’t write a single media query! I guess for small elements that aren’t a huge focus of the page that’s a good thing, but I really had to fight the urge to try and fine-tune this layout with a media query. But it does show me that a responsive website can be build without media queries. It is possible, but maybe not the best developer experience.

Demo & final code

Responsive grid without media queries demo

// index.jsx
import React from 'react';
import * as styles from './index.module.css';
import fern from './fern.jpg';
import leaf from './leaf.jpg';
import plants from './plants.jpg'


const Index = () => (
  <div className={styles.wrapper}>
    <div className={styles.grid}>
      <div>
        <img className={styles.image} src={fern} alt="fern" />
      </div>
      <div className={styles.text_square}>
        <div className={styles.title_text}>Awesome title</div>
        <div className={styles.subtitle_text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </div>
      </div>
      <div>
        <img className={styles.image} src={leaf} alt="leaf" />
      </div>
      <div className={styles.text_square}>
        <div className={styles.title_text}>Awesome title</div>
        <div className={styles.subtitle_text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </div>
      </div>
      <div>
        <img className={styles.image} src={plants} alt="plants" />
      </div>
      <div className={styles.text_square}><div className={styles.title_text}>Awesome title</div>
        <div className={styles.subtitle_text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </div>
      </div>
    </div>
  </div>
);

export default Index;
/* index.module.css */
.wrapper {
  max-width: 950px;
  margin-left: auto;
  margin-right: auto;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 375px), 1fr));
  gap: 2px;
}

.image {
  height: 350px;
  width: 100%;
}

.text_square {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 350px;
  padding: 1rem;
}

.title_text {
  font-size: 32px;
  font-weight: 600;
}

.subtitle_text {
  opacity: 0.8;
}