CSS-grid: automatically span items - css

Is there a way to make a grid item span over all free space automatically?
For example here, the grid has two rows and two columns. In the first row, the second column is free, so I'd like to span the item (as it does when I explicitly set grid-column-end: 3).
But I do not want to calculate the spans, but let the browser do that.
Edit: to make the question clearer: the container is only allowed to specify the number of rows and cols. The items are only allowed to specify the row- and col-start but no spans and -ends so if there is an item with row-start: i followed by an item with row-start: i+1+n and no items with row-start: i+1+m with m<n the item with row-start: i should automatically span n.
<div style="display: grid; gap: 5px;">
<div style="
background: red;
grid-row-start: 1;
grid-column-start: 1;
/* how can I get rid of this */
grid-column-end: 3;">1 1</div>
<div style="
background: red;
grid-row-start: 2;
grid-column-start: 1;">2 1</div>
<div style="
background: red;
grid-row-start: 2;
grid-column-start: 2;">2 2</div>
</div>

First off,
You cannot automatically span grid items over all free space.
Without explicitly setting the grid-column-end property or calculating the spans yourself - it would be best to resort to something like flexbox.
The way the grid layout works is that it uses the grid-row-start, grid-column-start, grid-row-end and grid-column-end properties to determine the size and position of each item within the grid. Without explicitly setting these properties, the browser will not know how to position the items and you'll inevitably end up with a gigantic mess. Grids aren't like flexbox since they aren't really smart and require you to explicitly set nearly every aspect of it in order to be rendered properly. They aren't really responsive either.
If you do decide to continue sticking with CSS Grid, one option would be to use JavaScript to calculate the spans based on the position of the other items on the grid and then set these properties dynamically. This is more complex and will add more overhead to your code - making it less easy to maintain
Another option (which I personally recommend) is to use flexbox instead of CSS Grid. A flexbox automatically adjusts the size of its flex items based on the available space, and you can use the flex-wrap property to wrap the items onto new rows if there is not enough space.
Flexbox is also responsive. CSS Grid is not.
Therefore, the pros weigh out the cons when compared to CSS Grid.

I don't know if that can totally suits you but one way would be to defined class with span 2 and span1.
.row {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: 1fr auto;
}
.col1 {
grid-column: auto / span 2;
}
.col2 {
grid-column: auto / span 1;
}
<div class="row">
<div class="col1" style="background-color: red">
1 1
</div>
</div>
<div class="row">
<div class="col2" style="background-color: green">
2 1
</div>
<div class="col2" style="background-color: blue">
2 2
</div>
</div>

we can put css grid-column: 1 / -1; as it will automatically fill the cell to the last. So basically we don't need to provide grid-column-end with specific number.
.row {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.col-stretch{
grid-column: 1 / -1;
}
<div class="row">
<div class="col-stretch" style="background-color: red">
1 1
</div>
</div>
<div class="row">
<div class="col" style="background-color: green">
2 1
</div>
<div class="col" style="background-color: gray">
2 2
</div>
<div class="col" style="background-color: yellow">
2 3
</div>
</div>

Related

How to place nested DIVs in a CSS grid with variable row heights?

I need to place 4 div containers in a 2 by 2 matrix. The width of the columns must be equal (and is therefore fixed), while the height of the rows must adapt itself to the content of the cells (and is therefore variable).
This is simple to do as long as the markup structure looks something like this:
<div class="wrapper">
<div class="cell a1">...</div>
<div class="cell a2">...</div>
<div class="cell b1">...</div>
<div class="cell b2">...</div>
</div>
The corresponding CSS would look like this:
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
}
Unfortunately, my markup (which I cannot change easily) contains the cells in a nested markup structure:
<div class="wrapper">
<div class="container">
<div class="cell a1">...</div>
<div class="cell a2">...</div>
</div>
<div class="container">
<div class="cell b1">...</div>
<div class="cell b2">...</div>
</div>
</div>
As long as the height of the two rows can be equal, declaring .container as secondary grid solves the issue. But since the row height must be adjusted according to the cell content, this doesn't work.
Is there a way to place all four div.cell in the same grid defined by div.wrapper, although they are not direct child elements?
What you are looking for is Subgrid, feature currently (December 2021) only tested on Firefox Nightly.
Info about this CSS attribute (from the Mozilla Web Docs page) :
When you add display: grid to a grid container, only the direct children become grid items and can then be placed on the grid that you have created.
You can "nest" grids by making a grid item a grid container. These grids however are independent of the parent grid and of each other, meaning that they do not take their track sizing from the parent grid. This makes it difficult to line nested grid items up with the main grid.
For example, if you use grid-template-columns: subgrid and the nested grid spans three column tracks of the parent, the nested grid will have three column tracks of the same size as the parent grid.
When the feature will be available and supported by multiple browsers this example below will work (I guess):
html, body {
width: 100%;
height: 100%;
margin: 0;
}
.wrapper {
display: grid;
height: 100vh;
width: 100vw;
background: grey;
grid-auto-flow: rows;
grid-template-columns: auto auto;
grid-template-rows: auto auto;
}
.container {
display: grid;
grid-column: 1 / 3;
grid-row: 1 / 3;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
}
.a1{
background-color: blue;
grid-row: 1;
grid-column: 1;
}
.a2{
background-color: yellow;
grid-row: 1;
grid-column: 2;
}
.b1 {
background-color: red;
grid-row: 2;
grid-column: 1;
}
.b2 {
background-color: green;
grid-row: 2;
grid-column: 2;
}
<div class="wrapper">
<div class="container a">
<div class="cell a1">A1</div>
<div class="cell a2">A2</div>
</div>
<div class="container a">
<div class="cell b1">B1</div>
<div class="cell b2">B2</div>
</div>
</div>
And will render something like this :

Wrapping grid in bootstrap with different column sizes

I’d like to set up a page with multiple entries consisting of some description text and associated icons. The icons may vary in size and should be aligned. (I use letters “i” and “w” instead of icons for simplicity in my examples below.)
When the page is wide enough, I’d like them to be set up as a grid with a wide left column and the icons to the right, that should take as few horizontal space as possible. Here is an example with two “icons”.
.container {
display: grid;
grid-template-columns: 1fr repeat(2, max-content);
}
.content {
justify-self: center;
}
<div class="container">
<div class="header">Some text</div>
<div class="content">i</div>
<div class="content">i</div>
<div class="header">Some more text</div>
<div class="content">w</div>
<div class="content">w</div>
</div>
When the viewport is too small, so that the left column would be reduced, say, below 200 px, I’d like to switch responsively the layout and have it displayed as a stack, as in this example.
.container {
display: grid;
grid-template-columns: 1fr repeat(2, max-content) 1fr;
}
.container > div {
justify-self: center;
}
.header {
grid-column-start: 1;
grid-column-end: 5;
}
.content1 {
grid-column-start: 2;
}
.content2 {
grid-column-start: 3;
}
<div class="container">
<div class="header">Some text</div>
<div class="content1">i</div>
<div class="content2">i</div>
<div class="header">Some more text</div>
<div class="content1">w</div>
<div class="content2">w</div>
</div>
This approach works, but there are multiple aspects that I’d like to improve, if possible.
My web site uses bootstrap, so I could use their “row” and “col” functionalities (or other bootstrap concepts). Designing my own grid instead feels like not using the right tools for the job. But I could not find out how to design such a grid with bootstrap’s rows and columns. (This question raises a similar problem.)
My approach requires an explicit media query and uses two completely different designs depending on the available space. This feels more complex than necessary. Is it possible to make better use of the grid module responsiveness (or any other appropriate html or css trickery), so that the icons would automatically flow below the text when the viewport is too small? I thought about using auto-fill, but as my columns do not all have the same size, I ignore how to proceed.
My current design for the small viewport case uses classes content1, content2, and so on, and repetitive CSS instructions to place them in successive columns. This problem will be exacerbated if I want more icons. Can I avoid such repetition?
The display classes are responsive. Therefore you can use d-flex d-md-grid on the container. When it switches to display:flex the grid-template-columns will be ignored.
.container {
grid-template-columns: 1fr repeat(2, max-content);
}
.content {
justify-self: center;
}
<div class="container d-flex d-md-grid flex-wrap align-items-center justify-content-center text-md-start text-center">
<div class="header w-100">Some text</div>
<div class="content">i</div>
<div class="content">i</div>
<div class="header w-100">Some more text</div>
<div class="content">w</div>
<div class="content">w</div>
</div>
Demo on Codeply
Of course, you could use d-sm-grid, d-lg-grid or d-xl-grid instead of d-md-grid to set the breakpoint as desired.

dynamic css grid layout, keeping some elements on the same line

I have started using css grids recently (https://css-tricks.com/snippets/css/complete-guide-grid/) but feel like I'm not fully exploiting them. My question concerns a situation where my displayed data is dynamic. For instance I want to display this:
In short:
each row of data can contain one or two columns
rows are separated by a divider row
data is dynamic, the css grid layout cannot be pre-set
It feels like css grids have a way of doing this (without dynamically generating the css), using repeat etc... but I don't really see how.
This is done under React, some dynamic rows are generated using a map, the code would looks something like this:
return (
<div>
<div> 1A </div>
<div> 1B </div>
<MyDivider />
<div> 2 </div>
<MyDivider />
{myData.map((row) => (
<div key={row.key}>
<div>{row.A}</div>
<div>{row.B}</div>
<MyDivider />
</div>
))}
// more rows, with single (merged) or dual columns...
</div>
);
Is it possible to do this without dynamically generating the grid css? Namely, defining grid settings for single / dual column lines, and divider lines? Also, due to the map function, under React, my dynamic content ends up wrapped in a div; do I need to break this down in multiple map's so that I don't have this wrapping div?
Thanks!
Edit 10/24/2020:
To clarify things, I don't want to literally display what is in this drawing, this is just a layout, with each '1A', '1B' block corresponding to some data cell, just like in a tabulator.
Assuming that you know if a row is single or dual columns.
You can make a cell span by using grid-column like the sample below
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5px; // for demonstration purposes
}
.grid-item {
background-color: lightblue; // for demonstration purposes
}
.grid-item.single {
grid-column: span 2;
}
.grid-separator {
grid-column: span 2;
background-color: lightgreen; // for demonstration purposes
}
<div class="grid">
<div class="grid-item">A1</div>
<div class="grid-item">B1</div>
<div class="grid-separator">Separator</div>
<div class="grid-item single">A2</div>
<div class="grid-separator">Separator</div>
<div class="grid-item">A3</div>
<div class="grid-item">B3</div>
<div class="grid-separator">Separator</div>
<div class="grid-item">A4</div>
<div class="grid-item">B4</div>
<div class="grid-separator">Separator</div>
<div class="grid-item single">A5</div>
</div>
Note that you need to add the single class under some condition
Please show us your css or you can do it like that way...
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
.grid div {
text-align: center;
background: #ddd;
margin: 2px;
padding: 10px;
}
.mydevider {
grid-column: 1 / span 2;
}
<div class="grid" >
<div> 1A </div>
<div> 1B </div>
<div class="mydevider">
<div> 1A </div>
</div>
<div> 1B </div>
<div> 1B </div>
<div class="mydevider">
<div> 1A </div>
</div>
<div> 1B </div>
<div> 2 </div>
</div>

Make a grid column span the entire row

Imagine we have 2 CSS Grid containers with dynamic columns count based on width.
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
The grid works perfectly, but what if we need to have another grid to have the 1st column to be same as in another grid with the code shown above, but it's another column to span through more cells - depending on how many cells are in the current row.
To better understand issue, there are images:
On more narrow wrapper:
We would need to apply something like grid-column: span ALL (if something like that exists), with meaning that ALL = till the end of current row.
What is really important is that "First" column should always align with "1" column.
Code to run example is here:
.grid div {
/* Not important fancy styles */
height: 40px;
text-align: center;
padding-top: 20px;
}
.grid {
width: 350px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
background-color: silver;
}
.grid-second {
background-color: red;
}
.grid-another {
background-color: purple;
border: 1px solid gray;
}
<div class="grid">
<div class="grid-first">
First
</div>
<div class="grid-second">
Second (Want till end)
</div>
</div>
<!-- Another same grid -->
<div class="grid">
<div class="grid-another">
1
</div>
<div class="grid-another">
2
</div>
<div class="grid-another">
3
</div>
<div class="grid-another">
4
</div>
</div>
PS. please do not post solutions using media query. I am interested in any (even little hacky solution), which will work without usage of media queries.
Here are two interesting sections in the CSS Grid specification:
7.1. The Explicit Grid
Numeric indexes in the grid-placement properties count from the edges
of the explicit grid. Positive indexes count from the start side,
while negative indexes count from the end side.
also here...
8.3. Line-based Placement: the grid-row-start, grid-column-start, grid-row-end, and grid-column-end properties
If a negative integer is given, it instead counts in reverse, starting
from the end edge of the explicit grid.
In other words, when dealing with an explicit grid, which means a grid defined by these properties:
grid-template-rows
grid-template-columns
grid-template-areas
grid (which is the shorthand for the three properties above, among others)
... you can make a grid area span all columns by setting this rule:
grid-column: 1 / -1;
That tells the grid area to span from the first column line to the last column line, which I believe meets your stated objective:
"We would need to apply something like grid-column: span ALL (if something like that exists), with meaning that ALL = till the end of current row."
jsFiddle demo
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
background-color: silver;
}
.grid-second {
grid-column: 2 / -1;
background-color: red;
}
/* Not important fancy styles */
.grid div {
height: 40px;
text-align: center;
padding-top: 20px;
}
.grid-another {
background-color: purple;
border: 1px solid gray;
}
<div class="grid">
<div class="grid-first">First</div>
<div class="grid-second">Second (Want till end)</div>
</div>
<!-- Another same grid -->
<div class="grid">
<div class="grid-another">1</div>
<div class="grid-another">2</div>
<div class="grid-another">3</div>
<div class="grid-another">4</div>
<div class="grid-another">1</div>
<div class="grid-another">2</div>
<div class="grid-another">3</div>
<div class="grid-another">4</div>
<div class="grid-another">1</div>
<div class="grid-another">2</div>
<div class="grid-another">3</div>
<div class="grid-another">4</div>
</div>

Flexbox grid: cells won't grow

I'm trying to create a grid layout based on flexbox:
.container {
width: 360px; /* Try to change this. Possible values: 240px, 360px, 480px.*/
background: #eee;
display: flex;
flex-flow: row wrap;
margin: 0 -10px;
}
.cell {
box-sizing: border-box;
padding: 10px;
flex: 0 0 120px;
max-width: 120px;
}
.cell.wide {
flex: 1 0 120px;
max-width: 240px;
}
.inner {
background: red;
height: 100px;
color: #fff;
padding: 10px;
box-sizing: border-box;
}
.wide .inner {
background: blue;
}
<div class="container">
<div class="cell">
<div class="inner">1</div>
</div>
<div class="cell wide">
<div class="inner">2</div>
</div>
<div class="cell">
<div class="inner">3</div>
</div>
<div class="cell">
<div class="inner">4</div>
</div>
<div class="cell wide">
<div class="inner">5</div>
</div>
<div class="cell">
<div class="inner">6</div>
</div>
<div class="cell wide">
<div class="inner">7</div>
</div>
<div class="cell">
<div class="inner">8</div>
</div>
<div class="cell">
<div class="inner">9</div>
</div>
<div class="cell">
<div class="inner">10</div>
</div>
<div class="cell">
<div class="inner">11</div>
</div>
<div class="cell wide">
<div class="inner">12</div>
</div>
<div class="cell">
<div class="inner">13</div>
</div>
</div>
This JSFiddle helps to illustrate my problem.
A container element .container can contain any number of cells.cell. There are two types of cells: a regular one with a fixed width (the red ones in the jsfiddle) and a wide one .cell.wide (the blue ones) which are twice as wide but could shrink to the width of the regular cell if there is not enough space in the current row. Each row must be filled completely.
So in the example (see fiddle):
Cell #2 should be wide and push #3 to the next row.
In the second row, which then contains #3, #4 and #5, cell #5 should stay small, because there's no space for a larger cell and rows should be filled completely.
etc
By the way: The grid container is of variable width and rows can therefore contain between two and four cells. You can try this out in the fiddle by changing to width to the specified possible values.
After trying around in the fiddle for the whole morning and trying numerous combinations of the flex, width, min-width and max-width properties I definitely need you help! Thanks in advance!
I already have a Javascript workaround (counting columns and adding classes) but would much prefer a CSS-only solution.
I believe the answer is that what you're looking for is not possible with Flexbox. The reason is that the sizing algorithm (in simplified terms) uses a couple of steps to determine the actual size of the element:
Determine the preferred minimum size (the value of flex-basis if set to a length/percentage, the value of main axis size if declared to be auto, and min-content size if not). In your case, both .cell and .cell.wide prefer 120px.
Place items as if they had the preferred size.
Evaluate each resulting line (in a wrapping flex flow) and determine any remaining space.
Distribute remaining space (per line) according to flex-grow.
In your case, this means that each item (regardless if .wide or not) will evaluate to a preferred size of 120px, and thus fits 3 items on each row. At this point, the flex-grow factor does nothing, as there isn't any space left to grow, so the fact that .wide items have a flex-grow of 1 is irrelevant. I think your example would require a form of "sometimes min-width, sometimes not" behavior that flexbox just doesn't do — there would be too many layout passes for the browser to do (e.g. reflowing into lines several times etc).
The only CSS-based solution I can think of is to hardcode all the possible scenarios in something akin to "Quantity Queries", but that would get unwieldy fast...
Closest I can get with flexbox is something like https://jsfiddle.net/qde5xq09/1/.

Resources