Background
I am trying to build a page with main section containing a dynamic grid in between a header and footer. As users use the page, book shaped .grid-item divs are appended to the .grid. Initially, when the .grid is empty, I want the <main> to expand to fill the page space so the footer isn't floating half way up the page. As the grid grows beyond the confines of this space, I want the page to grow vertically so that it scrolls.
Book shape
By book shaped, I mean that the column should be 2/3 the size of the height for each grid item.
Problem
The problem is that only the columns of the grid are behaving as intended with the items equally sized. In the row direction, only the first row is working as intended and the following rows are only the height of the content. As a result, they are no longer book shaped.
Solutions tried
I added flex: 1 to the <main> so that it can grow as needed. I also used grid-template-rows: repeat(auto-fill, minmax(300px, 1fr)); so that it would auto-fill the grid with equally sized rows.
Following this, I tried to auto-fill the grid with static sized columns and rows:
grid-template-columns: repeat(auto-fill, 200px);
grid-template-rows: repeat(auto-fill, 300px);
After this, I did some reading on the repeat() function and auto-fill to see where my misunderstanding is. I also searched stack-overflow for similar issues and attempted some mentioned solutions like changing my flex-grow and flex-basis for the <main> but I could not resolve the issue.
Closest Solution
I found that if I change the rows explicitly instead of using auto-fill, then the rows work as intended.
grid-template-rows: repeat(12, minmax(250px, 1fr));
This would however require for me to use javascript to change the styling of the .grid as I append children. This solution doesn't work because if the page width changes so that there are fewer columns, the rows would increase automatically, so it would add a lot of extra work to calculate the required rows using javascript before changing the styling. For this reason, I am ideally looking for a CSS only solution.
body {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 0;
}
header, footer {
height: 40px;
}
main {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
background-color: grey;
width: 100%;
}
.grid {
display: grid;
width: 60vw;
margin: 5px var(--margin-size) 5px var(--margin-size);
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(300px, 1fr));
gap: 10px;
}
.grid-item {
background-color: red;
}
<body>
<header>
Header
</header>
<main>
<div class="grid">
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
</div>
</main>
<footer>
Footer
</footer>
<body>
My question is how can I make the grid grow beyond the confines of the screen while retaining the book shape of the grid children?
I was able to get it working as intended with:
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: minmax(300px, 1fr);
Working snippet below:
body {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 0;
}
header, footer {
height: 40px;
}
main {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
background-color: grey;
width: 100%;
}
.grid {
display: grid;
width: 60vw;
margin: 5px var(--margin-size) 5px var(--margin-size);
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: minmax(300px, 1fr);
gap: 10px;
}
.grid-item {
background-color: red;
}
<body>
<header>
Header
</header>
<main>
<div class="grid">
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
<div class="grid-item">
Grid Item
</div>
</div>
</main>
<footer>
Footer
</footer>
<body>
Related
My scenario
I have these two flex containers (the difficulty options and the max-score options):
I want the 'easy', 'medium' and 'hard' button to share the same width, but also to fit they're content (in this case, because 'medium' is the longest, they should all equal its width).
I want the same behavior with the bottom buttons (but for them to have a smaller width since they need to accommodate for smaller content).
Right now the flex containers for both of them is set to:
display: flex;
flex-direction: wrap;
flex-wrap: wrap;
And the flex children are each set to their default flex values, with a set height and an auto width.
Approaches I've tried
First approach - flex-basis and flex-grow
Setting the children to flex-basis: 0 and flex-grow: 1, as I've seen in past questions, but then my wrapped child fills the entire width, and the top buttons aren't the same width:
Second approach - -- hardcoded flex-basis
Setting all children to flex-basis: 90px (90px to accommodate for the biggest button, 'medium') which does make them all the same width, but then the width is fixed and doesn't adjust to only fit the content (specifically this is desired so the score buttons can fit in two rows instead of three).
Third approach - max-width
The closest I've got to is to set the children to:
```
max-width: 90px;
flex-basis: 0;
flex-grow: 1;
```
Which makes them behave as wanted:
But when the screen width shrinks, the buttons start to differ in width (the obvious one is the '200' button bigger than the other scores, but also 'medium' is bigger than 'easy' and 'hard'):
My code:
.flex-col,
.flex-row {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.button {
border-style: solid;
padding: 4px;
}
.parent {
height: auto;
width: auto;
}
<div class="parent flex-col">
<div class="flex-col">
<div class="flex-row">
DIFFICULTY
</div>
<div class="flex-row">
<div class="button">EASY</div>
<div class="button">MEDIUM</div>
<div class="button">HARD</div>
</div>
</div>
<div class="flex-col">
<div class="flex-row">
MAX SCORE
</div>
<div class="flex-row">
<div class="button">50</div>
<div class="button">75</div>
<div class="button">100</div>
<div class="button">150</div>
<div class="button">200</div>
</div>
</div>
</div>
Help appreciated, thanks!
The closest way to do this with CSS only, is to use a grid instead of a flexbox for reasons well explained here.
The only way to truly do what you are asking (make all children have the same width as the widest child), is with JavaScript. Loop through the elements to find the biggest width and set them all to have the found width.
Here is a snippet demonstrating both concepts:
const equalizers = document.querySelectorAll('.equalize')
let r = 0
equalizers.forEach(equalizer => {
const widths = []
for (const btn of equalizer.children) {
const w = btn.getBoundingClientRect().width
// Math.ceil() is optional to avoid long floats
widths.push(Math.ceil(w)) // 82
// widths.push(w) // 81.31945037841797
}
const biggest = Math.max(...widths)
console.log(`biggest width found in row[${r++}]:`, biggest)
for (const btn of equalizer.children) {
btn.style.width = `${biggest}px`
}
})
.flex-col,
.flex-row {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.button {
border-style: solid;
padding: 4px;
}
.parent {
height: auto;
width: auto;
}
.grid-row {
display: grid;
gap: 4px;
}
.grid-row>* {
text-align: center;
}
#media (min-width: 25em) {
.grid-row {
grid-auto-flow: column;
grid-auto-columns: 1fr;
}
}
.flex-row>* {
text-align: center;
}
<hr>
<strong>JavaScript</strong> (only ever as wide as the widest sibling, with wrapping)
<hr>
<div class="parent flex-col">
<div class="flex-col">
<div class="flex-row">
DIFFICULTY
</div>
<div class="flex-row equalize">
<div class="button">EASY</div>
<div class="button">MEDIUM</div>
<div class="button">HARD</div>
</div>
</div>
<div class="flex-col">
<div class="flex-row">
MAX SCORE
</div>
<div class="flex-row equalize">
<div class="button">50</div>
<div class="button">75</div>
<div class="button">100</div>
<div class="button">150</div>
<div class="button">200</div>
</div>
</div>
</div>
<hr>
<strong>Grid</strong> (always as wide as posible and no wrapping, either all stacked, or all inline with breakpoint)
<hr>
<div class="parent flex-col">
<div class="flex-col">
<div class="flex-row">
DIFFICULTY
</div>
<div class="grid-row">
<div class="button">EASY</div>
<div class="button">MEDIUM</div>
<div class="button">HARD</div>
</div>
</div>
<div class="flex-col">
<div class="flex-row">
MAX SCORE
</div>
<div class="grid-row">
<div class="button">50</div>
<div class="button">75</div>
<div class="button">100</div>
<div class="button">150</div>
<div class="button">200</div>
</div>
</div>
</div>
I have a menu grid layout with an inner item (in this case it's the .metadata div) that I want to expand and push down another item. See example here :
.wrapper {
display: grid;
grid-column-gap: 8px;
grid-row-gap: 4px;
grid-template-columns: 48px minmax(0px, 3fr) 1fr;
grid-template-rows: 24px 20px 44px;
grid-template-areas:
"icon title action-bar"
"icon metadata action-bar"
"tabs .... bottom-right";
padding: 16px 16px 0 16px;
}
.metadata {
grid-area: metadata;
display: flex;
align-items: baseline;
direction: ltr;
}
.innterTest {
height: 100px;
background-color: red;
}
.metadataItem {
display: flex;
}
.tabs {
grid-area: tabs;
grid-column-end: 3;
padding-top: 4px;
}
<div class="wrapper">
<div class="icon">icon
</div>
<div class="title">TITLE
</div>
<div class="action-bar">action bar
</div>
<div class="metadata">
<div class="metadataItem">
data node 1
<div class="innterTest">
testing
</div>
</div>
<div class="metadataItem">
data node 2
</div>
<div class="metadataItem">
data node 3
</div>
<div class="metadataItem">
data node 4
</div>
</div>
<div class="tabs">tabs
</div>
</div>
https://jsfiddle.net/c8wx2bgn/
If you inspect the outer .metadata wrapping div it seems to stay a small size. What I would like to happen is for it to expand and push down the .tabs grid item. The general grid layout has been working as I had hoped, but I've added more items inside metadata and want it to push tabs down when it expands.
I've tried enforcing a height on the metadata and metadata divs but this does not seem to effect the layout. New to grid so unsure what I am missing here.
You have grid-template-rows: 24px 20px 44px.
This means that the second row, which contains your metadata div, is limited in height to 20px.
Try this: grid-template-rows: 24px auto 44px.
I'm trying to arrange a set of statistics such that:
they are displayed on a single horizontal line
the enclosing element is no wider than it needs to be to contain the content
there should be a fixed gap between statistics
I tried implementing this using display: grid. Here is my approach:
.outer {
background-color: yellow;
display: inline-block;
}
.stats {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
<div class="outer">
<div class="stats">
<div class="stat">
<strong>Value:</strong> 1,234,568
</div>
<div class="stat">
<strong>Another value:</strong> 98,765
</div>
<div class="stat">
<strong>Test value:</strong> 83,263
</div>
</div>
</div>
Unfortunately, this results in some strange overlapping. Despite there being plenty of room for the .outer element to expand, the statistics are wrapped over several lines and the text runs into other columns:
How can I avoid this problem? I tried adding:
.stat {
white-space: nowrap;
}
...but the text still "runs" together. What am I missing?
The main problem stems from this declaration:
grid-template-columns: repeat(auto-fit, minmax(0, 1fr))
You're setting the columns to shrink to zero width.
Also, 1fr does nothing here, because there is no free space in the container. (You have the primary container set to inline-block, i.e., min-width. This means no extra space.)
At a minimum, these commands appear to be confusing the inline-block algorithm.
Consider leaving the columns at auto width (a default setting).
And, perhaps, setting them to appear in the first row. (I used the grid-auto-flow: column technique.)
.outer {
background-color: yellow;
display: inline-block;
}
.stats {
display: grid;
grid-gap: 20px;
grid-auto-flow: column;
}
<div class="outer">
<div class="stats">
<div class="stat">
<strong>Value:</strong> 1,234,568
</div>
<div class="stat">
<strong>Another value:</strong> 98,765
</div>
<div class="stat">
<strong>Test value:</strong> 83,263
</div>
</div>
</div>
I have a design where the entire page has a 3x3 column layout, however, one area of the page goes from 3 columns to 2, by just having negative space where every 3rd column used to be, like so:
Even when you add more div elements, like so:
I'm thinking the way to achieve this is using css grid with grid-areas, however, when uncommenting the two lines below this doesn't seem to work:
.inner {
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(3, 1fr);
// grid-template-areas: "c c .";
> div {
// grid-area: c;
}
}
Am I going the right way about this or would using Flexbox be more appropriate?
Link to a Codepen
You can consider an empty element that will take the third column/first row and you will have the needed result:
.inner {
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(3, 1fr);
}
.inner div {
background:red;
height:100px;
}
.inner:after {
content:"";
grid-row:1;
grid-column:3;
}
<div class='container'>
<h2>Title</h2>
<div class='inner'>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
</div>
</div>
UPDATE
with more element you can try this:
.inner {
display: grid;
grid-column-gap: 2rem;
grid-template-columns: repeat(3, 1fr);
}
.inner div {
background:red;
height:100px;
margin-bottom:2rem; /*to replace row gap*/
}
.inner:after {
content:"";
grid-row:1 / span 50; /*take all the third column*/
grid-column:3;
}
<div class='container'>
<h2>Title</h2>
<div class='inner'>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
<div>
Hey
</div>
</div>
</div>
Consider the following snippet:
#container{
border: solid 1px black;
display: inline-grid;
grid-template-columns: repeat(3, auto);
}
<div id="container">
<div class="row">
<div class="item">A1111</div>
<div class="item">B1</div>
<div class="item">C1</div>
</div>
<div class="row">
<div class="item">A2</div>
<div class="item">B2222</div>
<div class="item">C2</div>
</div>
<div class="row">
<div class="item">A3</div>
<div class="item">B3</div>
<div class="item">C3333</div>
</div>
</div>
The end result is a table-like display where each item of every row is the width of the widest item in that column.
A1111 A2 A3
B1 B2222 B3
C1 C2 C3333
Which is great - but I need the table laid out as rows...
A1111 B1 C1
A2 B2222 C2
A3 B3 C3333
display: table solves this - but table has some drawbacks around spacing, alignments and so-on. Therefore, grid and flex looks attractive.
Alas I cannot figure out how to get the information laid out as desired.
Adding display: grid to .row helps the order of information, but doesn't retain the equal column widths.
The item content will vary, and so cannot use fixed widths and it is not desired that the grid/flex spans the entire page/containing width.
You can define which column the grid item should be using grid-column. This means the row doesn't require a containing row div.
Working example...
#container{
border: solid 1px black;
display: inline-grid;
grid-auto-flow: row;
grid-gap: 10px;
padding: 10px;
}
.col1{
grid-column: 1;
}
.col2{
grid-column: 2;
}
.col3{
grid-column: 3;
}
<div id="container">
<div class="col1">A11111</div>
<div class="col2">B1</div>
<div class="col3">C1</div>
<div class="col1">A2</div>
<div class="col2">B2222222</div>
<div class="col3">C2</div>
<div class="col1">A3</div>
<div class="col2">B3</div>
<div class="col3">C33333333</div>
</div>
</div>
Your main problem is your markup is too deep. You have table-like markup, three levels deep: table, rows, and cells. For grid layout, you don’t need the “row” elements at all.
When you use display: grid or display: inline-grid on an element, it makes that element a grid container. Each of its child elements then become grid items. Grid items will be laid out in the grid defined by their container.
You also said you want columns of equal width. For this, you should use the fr unit rather than auto for your column sizes:
grid-template-columns: repeat(3, 1fr);
…this will make each column one “fraction” unit wide.
#container{
border: solid 1px black;
display: inline-grid;
grid-template-columns: repeat(3, 1fr);
}
<div id="container">
<div class="item">A1111</div>
<div class="item">B1</div>
<div class="item">C1</div>
<div class="item">A2</div>
<div class="item">B2222</div>
<div class="item">C2</div>
<div class="item">A3</div>
<div class="item">B3</div>
<div class="item">C3333</div>
</div>
There's way which I would make it.
Attached JSFiddle(click)
.row {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
.row > .item {
display:block;
flex: 1 1;
border: 1px solid #000;
}
There's such great guide about flex; Click here.