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
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>
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.
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:
Why doesn't percentage padding / margin work on flex items in Firefox and Edge?
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>
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>
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">
Related
I've a dynamic list of elements generated by an external library which I do not have any control.
.item-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
position: relative;
}
.item {
background-color: red;
}
.item:nth-last-child(2),
.item:last-child {
position: absolute;
right: 0;
}
.item:last-child {
top: 57px;
}
<div class="item-wrapper">
<div class="item">1
</div>
<div class="item">2
</div>
<div class="item">3
</div>
<div class="item">4
</div>
<div class="item">5
</div>
<div class="item">6
</div>
</div>
And I need to generate a layout where the last two elements always need to wrap and align to right side as below.
I tried using flexbox to achieve the desired layout and here's a bit of snippet I've written.
With the above css, the second last element of top row always overlaps to the last element of top row.
I've aware that whenever absolute positioning is used, it takes the element out of the flow and absolutely position them.
However, I do not have much knowledge on how to achieve the layout as given above.
I'm really flexible with other css approach if it's not possible with flexbox.
So, For your output what I did is I wrap all the items in flexbox layout.
Main logic for this layout is we need all row 3 child. and same space to last 2 child to align them at right.
To get that space I have added .item-wrapper with padding-right: calc(100%/4 - 20px);.
Now need each time last two elements to align right, so I just set .item-wrapper to position:relative and than set both last to child with position:asbolute and set second last to top:0 and last on to bottom:0.
to fulfil the desired width I just divided height for second last by 4 and removed that height from 100% for last child.
* {
box-sizing: border-box;
}
.item-wrapper {
display: flex;
flex-wrap: wrap;
gap: 5px;
padding-right: calc(100%/4 - 20px);
position: relative;
}
.item {
flex: 1 0 calc(100%/3 - 20px);
padding: 20px;
background-color: gray;
}
.item:nth-last-child(2),
.item:last-child {
position: absolute;
right: 0;
width: calc(100%/4 - 25px);
height: calc(50% - 2px);
}
.item:nth-last-child(2) {
top: 0;
height: calc(100%/4 - 5px);
}
.item:last-child {
bottom: 0;
right: 0;
height: calc(100% - 100%/4);
}
<div class="item-wrapper">
<div class="item">1
</div>
<div class="item">2
</div>
<div class="item">3
</div>
<div class="item">4
</div>
<div class="item">5
</div>
<div class="item">6
</div>
<div class="item">7
</div>
<div class="item">8
</div>
<div class="item">9
</div>
<div class="item">10
</div>
<div class="item">11
</div>
<div class="item">12
</div>
</div>
This may look a bit simpler as a grid.
Each item is placed in its desired column, the last two being special and the grid flow set to dense so the second to last element can start back at the top.
.item-wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-flow: dense;
}
.item:nth-child(3n+1) {
grid-column: 1;
}
.item:nth-child(3n+2) {
grid-column: 2;
}
.item:nth-child(3n) {
grid-column: 3;
}
.item:nth-last-child(2),
.item:last-child {
grid-column: 4;
}
<div class="item-wrapper">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
</div>
My challenge is:
I want to have a grid with a fixed amount of columns (which can later be adjusted via javascript) and a flexible amount of rows of equal height.
The number of rows are determined by the amount of grid items, which are UI cards.
These cards should fill out the entire height of their respective cell but MUST not increase the height of the row. So basically max-height = row-height assigned by grid
Then inside these cards we have the typical three parts: Header, Body and Footer. The body MUST be scroll-able, if more list items exists than the row-height allows.
I've tried to implement this on stackblitz
https://stackblitz.com/edit/angular-3gkmtm
What i don't understand is
Why the cards "stretches" the row when more items appear
How to achieve the scroll-able card body section without manually using a fixed height (like in the example i use max-height)
Why when there are more then 3 rows, it overflows
Please help!
<article>
<section>
<h2>Fixed Gird with scrollable cards</h2>
</section>
<section>
<button (click)="onAdd()">Add</button>
<button (click)="onRemove()">Remove</button>
</section>
<section class="remaining-height">
<div class="grid-container">
<div class="grid-item" *ngFor="let card of cards">
<div class="card">
<div class="card-header">Card #{{card}}</div>
<div class="card-body card-flexible-scroll">
<div class="list-item" *ngFor="let item of list">{{item}}</div>
</div>
<div class="card-footer">
Some Footer
</div>
</div>
</div>
</div>
</section>
</article>
article{
display: flex;
flex-direction: column;
height: 100%;
}
.remaining-height{
flex:1
}
.grid-container {
display: grid;
grid-gap: 0.5rem;
height: 100%;
grid-auto-rows: auto;
grid-template-columns: repeat(5, auto);
}
.grid-item{
display: flex;
padding:24px;
}
.card{
display: flex;
flex-direction: column;
width: 100%;
height:100%;
background:#ccc;
}
.card-body{
.list-item{
padding: 6px;
background:#fcd3d3;
}
.list-item:nth-child(even){
background:#efefef;
}
}
.card-flexible-scroll{
flex:1;
overflow-y:auto;
max-height: 300px; // <= no max height
}
Angular Controller to generate cards and list items
```js
export class AppComponent {
name = "Angular";
cards = new Array(8).fill(0).map((_,idx)=>idx+1);
list = new Array(30).fill(0).map((_,idx)=>idx+1);
onAdd() {
this.cards.push(this.cards.length + 1);
}
onRemove() {
this.cards.pop();
}
}
global style
html , body{
height: 100vh;
overflow: hidden;
}
If I understand correctly what you are trying to do you want that the card body takes all the available row space between the header and footer and not force the card to be bigger than the row if it contains items.
It is possible to achieve that with adding another div with absolute positioning inside the card body div that is sized to the full body height then the items inside will overflow correctly.
Here is the changed template:
<article>
<section>
<h2>Fixed Gird with scrollable cards</h2>
</section>
<section>
<button (click)="onAdd()">Add</button>
<button (click)="onRemove()">Remove</button>
</section>
<section class="remaining-height">
<div class="grid-container">
<div class="grid-item" *ngFor="let card of cards">
<div class="card">
<div class="card-header">Card #{{card}}</div>
<div class="card-body">
<div class="card-flexible-scroll">
<div class="list-item" *ngFor="let item of list">{{item}}</div>
</div>
</div>
<div class="card-footer">
Some Footer
</div>
</div>
</div>
</div>
</section>
</article>
And the updated CSS:
.card-body{
.list-item{
padding: 6px;
background:#fcd3d3;
}
.list-item:nth-child(even){
background:#efefef;
}
position: relative;
flex:1;
}
.card-flexible-scroll{
position: absolute;
width: 100%;
height: 100%;
max-height: 100%;
overflow-y: auto;
}
I created a fork of your StackBlitz where you can see how it works. If this is not what you look for please explain more.
I have a grid of products in which every grid have a width of 200 pixels. All I want it to work on all screen sizes.
My current screen size is 1366px. Moving to above screen sizes will left white space at right side.
flexbox justify-content: flex-start.
#content {
display: flex;
flex-wrap: wrap;
}
.tile {
height: 100px;
background: pink;
width: 200px;
}
<div id="content">
<div class="tile">1</div>
<div class="tile">2</div>
<div class="tile">3</div>
<div class="tile">4</div>
<div class="tile">5</div>
<div class="tile">6</div>
<div class="tile">7</div>
<div class="tile">8</div>
<div class="tile">9</div>
<div class="tile">10</div>
<div class="tile">11</div>
<div class="tile">12</div>
<div class="tile">13</div>
<div class="tile">14</div>
<div class="tile">15</div>
<div class="tile">16</div>
</div>
If the intent is for your product grid to fill the horizontal width of the screen regardless of screen resolution, you could opt for a "fluid" grid.
A "fluid" grid will retain it's structure and "stretch to fit" the width of the parent container (or screen resolution), what ever that may be:
#content {
display: flex;
flex-wrap: wrap;
}
.tile {
height: 100px;
background: pink;
/*
Specify percentage based with causes tile width to update dynamically
based on current width of parent. A width of 25% causes four tiles per
row.
width: 25%;
*/
/* For 6 tiles per row */
width: 16.6%;
}
<div id="content">
<div class="tile">1</div>
<div class="tile">2</div>
<div class="tile">3</div>
<div class="tile">4</div>
<div class="tile">5</div>
<div class="tile">6</div>
<div class="tile">7</div>
<div class="tile">8</div>
<div class="tile">9</div>
<div class="tile">10</div>
<div class="tile">11</div>
<div class="tile">12</div>
<div class="tile">13</div>
<div class="tile">14</div>
<div class="tile">15</div>
<div class="tile">16</div>
<div class="tile">17</div>
<div class="tile">18</div>
</div>
I am wondering if this is possible: I have a header that can contain a variable amount of text. Below that I have another element which I want to take up the remaining height of the page.
<div class="header row">
<div class="title column large-5">Potentially very long text</div>
<div class="menu column large-7">Menu items</div>
</div>
<div class="content">
</div>
<div class="footer">
</div>
Normally I would do this using calc, eg:
.content {
height: calc(100vh - 75px);
}
Where 75px is the set height of .header.
But in this example, the .header element is dynamic and does not have a set height. Only a padding and font-size are set.
To complicate things, this also uses the Foundation Grid layout, which makes me nervous about using display: table (.title and .menu sit side by side on desktop, but stacked on mobile) .
Is there anyway to get the height of the dynamic header element (without resorting to JQuery)?
You can use flexbox and set .content to flex-grow: 1 so that it will fill to grow the available space.
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
.content {
flex-grow: 1;
background: #eee;
}
<div class="header row">
<div class="title column large-5">Potentially very long text</div>
<div class="menu column large-7">Menu items</div>
</div>
<div class="content">
</div>
<div class="footer">
</div>
I made a small pen to show the way to do this using flex box, it involved changing your markup a bit:
css:
.container {
display: flex;
flex-direction: column;
height: 250px; // whatever you want here
}
.header {
width: 100%;
background: red;
padding: 10px;
}
.content {
background: yellow;
width: 100%;
flex-grow: 1;
}
So the content will always take the available space inside the content div.
check the whole pen: http://codepen.io/anshul119/pen/yMYeLa
hope this helps.
I'm trying to line up divs horizontally, even if they go off-screen. Im using display: box;
<div id="container">
<div class="box">
1
</div>
<div class="box">
2
</div>
....
....
</div>
.box{
background-color: #7f94a7;
color: #fff;
height: 5.2rem;
width: 6rem;
display:table;
}
#container{
display: box;
display: -webkit-box;
}
Check out the jsfiddle here.
This works fine in chrome but in internet explorer 10, the box are aligned vertically....
Not really sure what the display:table is supposed to do on the .box class -- there aren't any cells or rows inside of it, so it isn't table-like at all. The table box sizing model is made to flex the width of the cells. Take a look at this example to see display:table in action... resize the output panel to see what happens: http://jsfiddle.net/vz33sfwc/
.box{
display:table-cell;
}
#container{
display:table;
}
It sounds like you actually want the boxes to NOT flex, but to go off-screen. To do that, we'll set the boxes to use display:inline-block -- this makes them sit next to one another on a single line. Then, we tell the container how to treat white space for the inline elements with white-space: nowrap;
.box{
display: inline-block;
}
#container {
white-space: nowrap;
}
See it in action here, again resizing the output panel for the effect: http://jsfiddle.net/k9yp05mj/ -- notice we get a horizontal scrollbar and the boxes do not flex in size.
Documentation
CSS whitespace on MDN - https://developer.mozilla.org/en-US/docs/Web/CSS/white-space
CSS display on MDN - https://developer.mozilla.org/en-US/docs/Web/CSS/display
Change your display: box; to display: inline-box; and use overflow: to keep or hide run-off from being visible.
As stated above, white space: nowrap; on content fixes issue.
CSS:
.box{
background-color: #7f94a7;
color: #fff;
height: 5.2rem;
width: 6rem;
display: inline-block;
}
#container{
display: box;
display: -webkit-box;
overflow-x: auto;
white-space: nowrap;
}
try just with "display : inline" for the child div.keep width auto i.e depending on content it'll adjust Check the fiddle here - http://jsfiddle.net/invincibleJai/p5tebcua/45/
Code:
.box{
background-color: #7f94a7;
color: #fff;
height: 5.2rem;
width: 6rem;
display:inline;
padding:0.2em;
}
<div id="container">
<div class="box">
1
</div>
<div class="box">
2
</div>
<div class="box">
3
</div>
<div class="box">
4
</div>
<div class="box">
5
</div>
<div class="box">
6
</div>
<div class="box">
7
</div>
<div class="box">
8
</div>
<div class="box">
9
</div><div class="box">
10
</div>
<div class="box">
11
</div><div class="box">
12
</div>
<div class="box">
13
</div>
</div>