snazzybouche
snazzybouche

Reputation: 2426

Don't reserve space in a CSS Grid if the element doesn't exist / is optional

I'm using grid-template to set up a simple grid structure for one row and four columns. The two leftmost columns have a fixed width, and the remaining two should fill the remaining space.

However, the second column is optional - it may not be present at all. In this case I do not want to reserve any space for it. The two rightmost columns should fill the space.

This is obviously not possible with grid-template. Is it possible at all?

.grid {
  display: grid;
  grid-template-areas: "one two three four";
  grid-template-columns: 8rem 8rem 1fr 1fr;
}

.one   { background: #404788aa; grid-area: one;   }
.two   { background: #287d8eaa; grid-area: two;   }
.three { background: #3cbb75aa; grid-area: three; }
.four  { background: #dce319aa; grid-area: four;  }
<div class="grid">
  <div class="one">One</div>
  <div class="two">Two</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
</div>
<hr><p>Three and Four should fill the space:</p>
<div class="grid">
  <div class="one">One</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
</div>

Upvotes: 4

Views: 2836

Answers (3)

DirectionUnkown
DirectionUnkown

Reputation: 240

Flex is probably a better idea, but it is also quite possible to do it easily with the grid. This solution is based on fact that min-content will set second column width to 0, since there is no item there.

.grid {
  display: grid;
  grid-auto-columns: min-content min-content auto auto;
}

.one   { background: #404788aa; width: 8rem }
.two   { background: #287d8eaa; width: 8rem }
.three { background: #3cbb75aa; grid-column: 3; }
.four  { background: #dce319aa; grid-column: 4; }
<div class="grid">
  <div class="one">One</div>
  <div class="two">Two</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
</div>
<hr><p>Three and Four should fill the space:</p>
<div class="grid">
  <div class="one">One</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
</div>

Upvotes: 2

tnagy.adam
tnagy.adam

Reputation: 119

This solution merges the 3 and 4 column layout to a 5 column layout and uses a css trick to detect which class to use on the divs based on a certain existence and order. Then adjust the divs manually with grid-column property to align as needed.

This is horrible and it is absolutely not scalabe, but it uses grid and css only to solve the problem.

After reviewing my solution I was hesitant to share it at all, but then I thought it will be a good alternative to show how the flex solution (provided by @ng-hobby) is much better than this.

As @TylerH said the grid system is not a perfect solution since you have to define a structure before actually knowing how many elements you want to fit into that structure.

This is a visual to show how the 3 and 4 column layouts end up when merged together. The vertical lines show the grid lines and the numbers below are the names of those.

4 column layout

|  8rem  |  8rem  |  1fr (50% - 8rem)  |  1fr (50% - 8rem)  |
|        |        |                    |                    |

3 column layout

|  8rem  |    1fr (50% - 4rem)    |    1fr (50% - 4rem)     |
|        |                        |                         |

5 column (mixed) layout

|        |        |               |    |                    |
1        2        3               4    5                    6
|  8rem  |  8rem  |  50% - 12rem  |4rem|    50% - 8rem      |

From this we can calculate the grid-template-columns property. Note that I needed to switch to percents because calc() does not work with fr.

The (+) in css is called "Adjacent Sibling Selector" which in this case is used to apply a css rule when certain divs are next to one another.

.grid {
  display: grid;
  grid-template-columns: 8rem 8rem calc(50% - 12rem) 4rem calc(50% - 8rem) ;
}

.one   { background: #404788aa; grid-column: 1 / 2; }
.two   { background: #287d8eaa; grid-column: 2 / 3; }
.two + .three { background: #3cbb75aa; grid-column: 3 / 5; }
.three { background: #3cbb75aa; grid-column: 2 / 4; }
.four  { background: #dce319aa; grid-column: 4 / 6; }
.two + .three + .four { background: #dce319aa; grid-column: 5 / 6; }
<div class="grid">
  <div class="one">One</div>
  <div class="two">Two</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
  </div>
</div>

<hr><p>Three and Four should fill the space:</p>
<div class="grid">
  <div class="one">One</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
  </div>
</div>

Upvotes: 1

ng-hobby
ng-hobby

Reputation: 2199

Try using display: flex with flex attributes like this:

.flex {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
}

.one   { background: #404788aa; flex: 0 1 8rem; }
.two   { background: #287d8eaa; flex: 0 1 8rem; }
.three { background: #3cbb75aa; flex: 1 1 auto; }
.four  { background: #dce319aa; flex: 1 1 auto; }
<div class="flex">
  <div class="one">One</div>
  <div class="two">Two</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
</div>
<hr><p>Three and Four should fill the space:</p>
<div class="flex">
  <div class="one">One</div>
  <div class="three">Three</div>
  <div class="four">Four</div>
</div>

Upvotes: 3

Related Questions