78
votes

I am trying to create a responsive grid of squares. The squares should resize to fit the viewport's width. The squares should not resize when changing the viewport's height.

I got how to adjust the width of each square, but I don't know how to make the elements square and how to scale their height when the viewport width changes.

In the example at the fiddle below the seven squares should always fit horizontally, and they should scale as squares. I do not care how many rows are visible.

Fiddle here http://jsfiddle.net/gonyhvz8/11/

<body>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>
<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
</div>

.flex-container {
  padding: 0;
  margin: 0;
  list-style: none;
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
  -webkit-flex-flow: row;
  justify-content: space-around;
  height: 50px;
  line-height:30px;
}

.flex-item {
  background: tomato;
  margin: 5px;
  color: white;
  font-weight: bold;
  font-size: 1.5em;
  text-align: center;
  flex: 1 0 0px;
  height: auto;
}
6
It's not using flexbox, but this seems like it meets your other requirements: stackoverflow.com/a/20457076/717383 - James Montagne

6 Answers

112
votes

you should not set any size. you may use an extra element or a pseudo elemnt with vertical padding in %. this will allow you to use width as reference : a snippet to show:

.flex-container {
    padding: 0;
    margin: 0;
    list-style: none;
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-flex-flow: row;
    justify-content: space-around;
   
    line-height:30px;
}
.flex-item {
    background: tomato;
    margin: 5px;
    color: white;
    font-weight: bold;
    font-size: 1.5em;
    text-align: center;
    flex: 1 0 auto;
    height:auto;
}
.flex-item:before {
    content:'';
    float:left;
    padding-top:100%;
}
<body>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
</body>

an inline-block element could do too, just adapt the display/behavior of the box and it's content. the magic here comes from padding:50% 0; (100% vertical padding equals width of parent). see w3c about vertical margin and padding

[edit 07/2021]about centering the content inside that square for who ever needs this too (make the square itself a flex boxe too):

.flex-container {
    padding: 0;
    margin: 0;
    list-style: none;
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-flex-flow: row;
    justify-content: space-around;
   
    line-height:30px;
}
.flex-item {
    background: tomato;
    margin: 5px;
    color: white;
    font-weight: bold;
    font-size: 1.5em;
    text-align: center;
    flex: 1 0 auto;
    height:auto;
    
    display:flex;
    align-items:center;
    justify-content:center;
}
.flex-item:before {
    content:'';
    padding-top:100%;
}
<body>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
</body>

You may also think interesting : Responsive grid of squares within a responsive grid of squares & 4x4 grid of squares that scale up to a maximum width if this answer do not fully suits your square needs ;)

29
votes

For those that also want to use display: flex inside the square divs, you need to use display: table for the :before element, otherwise the square will work with Chrome but won't work with Firefox or Edge (as of Firefox 47 and Edge 13).

In the snippet bellow, which should work for all browsers, I also demonstrate how to wrap unlimited items with percentage columns (in this case 20%) and separate them with padding and inner divs, since margins with percentages do not work correctly in FF and of course Edge.

.flex-container {
  display: flex;
  justify-content: start;
  flex-wrap: wrap; 
}

.flex-cell {
  flex: 0 0 20%;
  display: flex;
  justify-content: center;
  align-items: stretch;
  padding: 0.5rem;
  box-sizing: border-box;
}

.flex-cell:before {
  content: '';
  display: table;
  padding-top: 100%;
}

.flex-item {
  flex-grow: 1;
  border: 1px solid black;
  background: tomato;
  color: white;
  
  display: flex;
  justify-content: center;
  align-items: center;
}
<body>
    <div class="flex-container">
        <div class="flex-cell">
            <div class="flex-item">1</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">2</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">3</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">4</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">5</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">6</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">7</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">8</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">9</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">10</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">11</div>
        </div>
        <div class="flex-cell">
            <div class="flex-item">12</div>
        </div>
    </div>
</body>
22
votes

The usual trick for maintaining aspect ratio is to use the fact that percentage values of margin and/or padding top & bottom are calculated based on the width of the containing element, and not the height.

You can see a non-flex version of this question HERE

However, you would like to have flex deal with the width of your elements, not set percentage based widths. For that, I have come up with a variation of that approach that will work with flex:

.flex-item:before {
    content: "";
    display: block;
    padding-top: 100%;
    float: left;
}

The trick here is to use the :before pseudo-element to insert a 0 width element with a padding-top of 100%. That 100% is 100% of the width, meaning this element with no content will be equally as tall as its container. This will stretch the flex-item to the same height as its width:

Additionally, you will need to remove the defined height from your flex-container or the height can't grow. You also may want to add min-width: 1.5em so that your elements don't shrink below the height of the character you have in each square.

http://jsfiddle.net/gonyhvz8/14/

.flex-container {
    padding: 0;
    margin: 0;
    list-style: none;
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-flex-flow: row;
    justify-content: space-around;
    line-height:30px;
}

.flex-item {
    background: tomato;
    margin: 5px;
    color: white;
    font-weight: bold;
    font-size: 1.5em;
    text-align: center;
    flex: 1 0 0px;
    min-width: 1.5em;
}
.flex-item:before {
    content: "";
	display: block;
	padding-top: 100%;
    float: left;
}
<body>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
    <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
        <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
        <div class="flex-container">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
    </div>
</body>
4
votes

Surely it is nice to use flexboxes, but the solution mentioned has a lack of flexability even it uses flexboxes. You are not able to adjust a floating of boxes. You are bounded to set the number of boxes per row before.

In this solution you can adapt a dynamic width of tiles/square and float them how you like. If you try this with flexboxes, you will get a problem with padding-top because it does not orientate on the given width of the containing blocks.

Solution without flexboxes but more flexability:

.flex-container {
  padding: 0;
  margin: -5px;
  font-size: 0;
  font-family: Helvetica, Arial, sans-serif;
}

.flex-item {
  position: relative;
  display: inline-block;
  height: 0;
  width: 100%;
  padding-top: 100%;
  height: auto;
  font-size: 20px;
  color: white;
  font-weight: bold;
  text-align: center;
}
@media (min-width: 480px) {
  .flex-item {
    width: 33.3333%;
    padding-top: 33.3333%;
  }
}
@media (min-width: 768px) {
  .flex-item {
    width: 25%;
    padding-top: 25%;
  }
}
.flex-item-inner {
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  margin: 10px;
  background: tomato;
}
<body>
  <div class="flex-container">
    <div class="flex-item">
      <div class="flex-item-inner">
        <div class="flex-item-inner-content">
          1
        </div>
      </div>
    </div>
    <div class="flex-item">
      <div class="flex-item-inner">
        <div class="flex-item-inner-content">
          2
        </div>
      </div>
    </div>
    <div class="flex-item">
      <div class="flex-item-inner">
        <div class="flex-item-inner-content">
          3
        </div>
      </div>
    </div>
    <div class="flex-item">
      <div class="flex-item-inner">
        <div class="flex-item-inner-content">
          4
        </div>
      </div>
    </div>
    <div class="flex-item">
      <div class="flex-item-inner">
        <div class="flex-item-inner-content">
          5
        </div>
      </div>
    </div>
    <div class="flex-item">
      <div class="flex-item-inner">
        <div class="flex-item-inner-content">
          6
        </div>
      </div>
    </div>
  </div>
</body>

Link to the codepen: http://codepen.io/dailysh-it/pen/xOVydO

4
votes

Simplified Version of G-Cyr's Answer

.flex-container {
  display: flex;
  flex-flow: row;
  justify-content: center;
}

.flex-item {
  background: tomato;
  margin: 10px;
  max-width: 50px;
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 0;
  height: auto;
}

.flex-item:before {
  /* The psuedo-element's padding-top percentage is based on the element's width. */
  padding-top: 100%;
  content: '';
  float: left;
}
<div class="flex-container">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item">3</div>
</div>
<div class="flex-container">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item">3</div>
  <div class="flex-item">4</div>
</div>
<div class="flex-container">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item">3</div>
  <div class="flex-item">4</div>
  <div class="flex-item">5</div>
</div>
<div class="flex-container">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item">3</div>
  <div class="flex-item">4</div>
  <div class="flex-item">5</div>
  <div class="flex-item">6</div>
</div>

Result

enter image description here

3
votes

There is a way to made it with no extra html elements also. I created a fiddle with your codebase http://jsfiddle.net/octavioamu/kq97pm3L/ but the tick is to use after elemento with display table

flex-item:after {
    content: ' ';
    padding-top: 100%;
    display: table;
}

You can use it in any grid, for example an square inside a column grid with bootstrap... Here is a sass mixin working inside a % grid column https://codepen.io/octavioamu/pen/xzeXGm

And here is with your code:

.flex-container {
  display: flex;
}

.flex-item {
  display: flex;
  width: 100%;
  justify-content: center;
  align-items: center;
  background-color: red;
  margin: 5px
}

.flex-item:after {
  content: ' ';
  padding-top: 100%;
  display: table;
}
<body>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
    <div class="flex-item">7</div>
  </div>
</body>