A grid layout with responsive squares
Asked Answered
E

5

59

I am wanting to create a grid layout with responsive squares.

I feel like I should be able to do this with CSS Grid layout but having trouble setting the height of each square to be equal to the width.

Also having trouble setting a gutter between each square.

Would I be better off using flexbox?

Currently my HTML looks like this but will be dynamic so more squares may be added. And of course it needs to be responsive so will ideally use a media query to collapse it to one column.

<div class="square-container">

  <div class="square">
    <div class="content">
    </div>
  </div>

  <div class="square">
    <div class="content spread">
    </div>
  </div>

  <div class="square">
    <div class="content column">
    </div>
  </div>

</div>

Using css grid, this is as far as I got

.square-container{
    display: grid;
    grid-template-columns: 30% 30% 30%;
    .square {

    }
}

I was able to get a bit further with flexbox and able to use space-between to align squares with a nice gutter but was still struggling to get the height to match the width of each square.

I wasn't able to find any examples of this being done with either flexbox or grid but any examples would be appreciated as well.

Thanks

Exchangeable answered 3/10, 2017 at 16:8 Comment(2)
I should add that within the content divs, I am wanting to have various layouts of img's and text using flexbox so I want to avoid having to use position or padding within the content div'sExchangeable
@kukkuz, you may want to consider keeping your answer here. It may one day be valid (when browser makers reach a consensus). Also, it will prevent others from posting the same solution.Guiana
A
54

The padding-bottom trick is the most used to accomplish that.

You can combine it with both Flexbox and CSS Grid, and since using percent for margin/padding gives inconsistent result for flex/grid items (on older browser versions, see edit note below), one can add an extra wrapper, or like here, using a pseudo, so the element with percent is not the flex/grid item.


Edit: Note, there's an update made to the specs., that now should give consistent result when used on flex/grid items. Be aware though, the issue still occurs on older versions.


Note, if you will add content to the content element, it need to be position absolute to keep the square's aspect ratio.

Fiddle demo - Flexbox

Edit 2: In a comment I were asked how to have a centered text, so I added that in below snippet.

.square-container {
  display: flex;
  flex-wrap: wrap;
}

.square {
  position: relative;
  flex-basis: calc(33.333% - 10px);
  margin: 5px;
  border: 1px solid;
  box-sizing: border-box;
}

.square::before {
  content: '';
  display: block;
  padding-top: 100%;
}

.square .content {
  position: absolute;
  top: 0; left: 0;
  height: 100%;
  width: 100%;

  display: flex;               /* added for centered text */
  justify-content: center;     /* added for centered text */
  align-items: center;         /* added for centered text */
}
<div class="square-container">

  <div class="square">
    <div class="content">
      <span>Some centered text</span>
    </div>
  </div>

  <div class="square">
    <div class="content spread">
    </div>
  </div>

  <div class="square">
    <div class="content column">
    </div>
  </div>

  <div class="square">
    <div class="content spread">
    </div>
  </div>

  <div class="square">
    <div class="content column">
    </div>
  </div>

</div>

CSS Grid version

.square-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));
  grid-gap: 10px;
}

.square {
  position: relative;
  border: 1px solid;
  box-sizing: border-box;
}

.square::before {
  content: '';
  display: block;
  padding-top: 100%;
}

.square .content {
  position: absolute;
  top: 0; left: 0;
  height: 100%;
  width: 100%;
}
<div class="square-container">

  <div class="square">
    <div class="content">
    </div>
  </div>

  <div class="square">
    <div class="content spread">
    </div>
  </div>

  <div class="square">
    <div class="content column">
    </div>
  </div>

  <div class="square">
    <div class="content spread">
    </div>
  </div>

  <div class="square">
    <div class="content column">
    </div>
  </div>

</div>
Acrosstheboard answered 3/10, 2017 at 17:50 Comment(6)
This seems to be working well across all browsers I've tested so far. Thank youExchangeable
this solution works like charm in any browser wow! and guess what I have implemented it with bootstrap and works like charmer!Transpire
LGSon, could you add a little explanation of what the magic is and how it works?Fourier
@Fourier The main magic is the "padding". To keep an element a square, its height and width should be the same, and why e.g. padding-top works, is that when setting a top (or bottom ) padding using percent, it uses its own width to calculate the padding's value, which also will become its height, hence it becomes a square. The second part, when it comes to its content, it needs in this case to be absolute positioned, so it doesn't affect the square's content, or else the padding calculation would need to also take that into account (and for that, a script would be needed).Acrosstheboard
how can i vertical align the text in the flex item?Fideicommissum
Updated the answer with a vertical aligned text sample.Acrosstheboard
G
15

Try using viewport percentage units.

jsFiddle

.square-container {
  display: grid;
  grid-template-columns: repeat(3, 30vw);
  grid-template-rows: 30vw;
  grid-gap: 2.5vw;
  padding: 2.5vw;
  background-color: gray;
}

.square {
  background-color: lightgreen;
}

body {
  margin: 0; /* remove default margins */
}
<div class="square-container">
  <div class="square">
    <div class="content"></div>
  </div>
  <div class="square">
    <div class="content spread"></div>
  </div>
  <div class="square">
    <div class="content column"></div>
  </div>
</div>

From the spec:

5.1.2. Viewport-percentage lengths: the vw, vh, vmin, vmax units

The viewport-percentage lengths are relative to the size of the initial containing block. When the height or width of the initial containing block is changed, they are scaled accordingly.

  • vw unit - Equal to 1% of the width of the initial containing block.
  • vh unit - Equal to 1% of the height of the initial containing block.
  • vmin unit - Equal to the smaller of vw or vh.
  • vmax unit - Equal to the larger of vw or vh.
Guiana answered 3/10, 2017 at 16:13 Comment(5)
Unfortunately .square-container is nested within a few containers so using vw causes the squares to overflow outside the .square-container wrapperExchangeable
Post a full demo. That may be a problem that can be overcome.Guiana
I really prefer this answer as it doesn't require the padding trick.Manzanares
what's the browser support like for this solution?Fideicommissum
If there is some vertical scrollbar, that solution dosn't work anymore, as 100vw becomes equal to the body width + the scrollbar width. Here is the same example as above with a vertical scrollbar that breaks the display : jsfiddle.net/0obt9cvs/3Galateah
T
8

You can use the fact that padding is calculated based on the width and set padding-top: 100% directly to the square grid items (the grid items would be square now).


2019 update

Note that for flex items as well as grid items earlier this doesn't used to work - see the post linked in the comments to this answer:

Now that there is a consensus between browsers (newer versions) to have the same behaviour for padding for flex items and grid items, you can use this solution.

See demo below:

.square-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));
  grid-gap: 10px;
}

.square {
  background: cadetblue;
  padding-top: 100%; /* padding trick directly on the grid item */
  box-sizing: border-box;
  position: relative;
}

.square .content { /* absolutely positioned */
  position: absolute;
  top: 0;
  right:0;
  left: 0;
  bottom: 0;
}
<div class="square-container">
  <div class="square">
    <div class="content"> some content here</div>
  </div>
  <div class="square">
    <div class="content"> some content here</div>
  </div>
  <div class="square">
    <div class="content"> some content here</div>
  </div>
  <div class="square">
    <div class="content"> some content here</div>
  </div>
  <div class="square">
    <div class="content column">some content here and there is a lot of text here</div>
  </div>
  <div class="square">
    <div class="content spread">text</div>
  </div>
  <div class="square">
    <div class="content column">some text here</div>
  </div>
</div>
Thrombokinase answered 3/10, 2017 at 16:22 Comment(5)
"Authors should avoid using percentages in paddings or margins on grid items entirely, as they will get different behavior in different browsers." https://mcmap.net/q/37736/-percentage-padding-margin-on-grid-item-ignored-in-firefox/3597276Guiana
Try it on Firefox.Guiana
Same problem with flexbox: https://mcmap.net/q/36850/-why-doesn-39-t-percentage-padding-margin-work-on-flex-items-in-firefox-and-edge/3597276Guiana
as the solution is now valid, I have un-deleted the answer, thanks all :)Thrombokinase
This is a perfect responsive square grid that worked very well.Kwok
D
7

You can achieve this in all modern browsers using CSS aspect-ratio property.

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 5px;
}

.container div {
  aspect-ratio: 1 / 1;
  
  /* Styles below just for demo */
  background-color: orange;
  color: white;
  font-family: Arial;
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="container">
  <div>A</div>
  <div>B</div>
  <div>C</div>
  <div>D</div>
  <div>E</div>
  <div>F</div>
  <div>G</div>
</div>
Damascene answered 20/4, 2022 at 10:53 Comment(1)
Note that this requires Safari 15+. See caniuse.com/mdn-css_properties_aspect-ratioChacha
P
-1

For days I was astonished that in 2020 there is no simple solution for this. I was convinced that with CSS grid this is gonna be a piece of cake... Flexbox solution provided by Ason is the only one that works across browsers. On Stack I found one more solution with CSS grid that uses padding-bottom: 100% but it doesn't work in Firefox (you get a lot of white space beneath the footer).

This is my take on the problem, I think it is the simplest solution of all that I have encountered these days.

CSS Grid solution on Codepen: https://codepen.io/abudimir/pen/ExKqyGp

<div class="square-container">
Prather answered 3/10, 2020 at 7:35 Comment(1)
you are using square images - so this doesn't work with images which are not square-scaled;Hildegard

© 2022 - 2024 — McMap. All rights reserved.