17

I've created a simple CSS Grid, I decided to not specify grid-template, grid-template-columns, grid-template-rows properties.

Instead, I started with grid-template-areas, and assigned area names to the grid-items via grid-area property.

After that, I was interested in what would happen if I remove grid-item from grid-template-areas. The result was kind of strange.

The removed grid-item was placed on the right and separated by additional column.

The problem:
enter image description here

Why did this happen? Is this expected behaviour or did I miss something in my code? How can I remove this column?

body {
  display: grid;
  grid-template-areas: 
     "header"
     "footer";
}

header {
  grid-area: header;
  background: lightblue;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: blue;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
user3789797
  • 450
  • 6
  • 15
  • Posting a bounty for @TemaniAfif for an exceptionally well-researched and detailed answer. Additional answers are welcome and will be considered for a part of the bounty. – Michael Benjamin Sep 15 '19 at 14:02
  • 1
    @Michael_B thanks :) but you also have done a big part of the work finding the culprit and that was the starting point of my answer ;) – Temani Afif Sep 15 '19 at 14:06

2 Answers2

18

There are four parts to this answer. The first three help explain the fourth, which covers the reason for the extra column. If you're only interested in the answer, skip to the end.

Contents:

  1. More than meets the eye: There's also an extra row!
  2. The grid-area property.
  3. The grid-template-areas property.
  4. The placement of unreferenced grid areas.

1. More than meets the eye: There's also an extra row!

You've only partially defined the problem. Yes, there's an extra column. But there's also an extra row.

Because you haven't defined a height on the grid container, the height defaults to auto – the height of the content (more details). So any rows with no content simply collapse and are invisible.

This issue doesn't exist with width because, in this case, you're using a block-level container (created by display: grid), which is designed to consume the full width of its parent, by default (more details).

So that's why you're not seeing the extra row. If you give the container some height, the row will appear.

body {
  display: grid;
  grid-template-areas:
    "header"
    "footer";
  height: 150px; /* new */ 
}

enter image description here

body {
  display: grid;
  grid-template-areas:
    "header"
    "footer";
  height: 150px; /* new */
}

header {
  grid-area: header;
  background: aqua;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: lightgreen;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

Note: If you had used display: inline-grid, both the extra row and the extra column would have been invisible.

body {
  display: inline-grid;
  grid-template-areas:
    "header"
    "footer";
}

enter image description here

body {
  display: inline-grid; /* adjustment */
  grid-template-areas:
    "header"
    "footer";
}

header {
  grid-area: header;
  background: aqua;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: lightgreen;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

2. The grid-area property.

Giving a name to the grid-area property creates a named line for each side of the area.

For example, grid-area: header resolves, in order, like this:

  • grid-row-start: header
  • grid-column-start: header
  • grid-row-end: header
  • grid-column-end: header

Like margin, border and padding, the grid-area property is a shorthand property. Unlike those properties, grid-area has a counterclockwise resolution order (in LTR languages), as illustrated above.

Because named grid areas occupy space, they need rows and columns in which to exist. Hence, named grid areas always impact the layout, even when they're not referenced in grid-template-areas.

So all that is necessary to "fix" your layout is to remove grid-area: main.

main {
  /* grid-area: main; */
  background: darkorange;
}

enter image description here

body {
  display: grid;
  grid-template-areas:
    "header"
    "footer";
}

header {
  grid-area: header;
  background: aqua;
}

main {
  /* grid-area: main; */
  background: darkorange;
}

footer {
  grid-area: footer;
  background: lightgreen;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

3. The grid-template-areas property.

Rows and columns (a/k/a tracks) that are created using grid-template-rows, grid-template-columns or grid-template-areas belong to the explicit grid. Any tracks not defined by those properties belong to the implicit grid (source).

For every string listed in grid-template-areas, a new row is created.

For every name or sequence of dots (...) in the string, a new column is created (but this doesn't apply in this case because each string has only one name).

Your code creates an explicit grid with two rows and one column:

body {
  display: grid;
  grid-template-areas: 
    "header"
    "footer";
}

enter image description here

As you can see in the image, header and footer have their own rows and exist in column one, exactly as defined in grid-template-areas.

The additional two rows and two columns are part of the implicit grid.

We can verify this by sizing them.

grid-template-columns works only on explicit columns.

grid-auto-columns works mostly on implicit columns (see note below).

body {
  display: grid;
  grid-template-areas: "header" "footer";
  grid-template-columns: 1fr;
  grid-auto-columns: 100px;
  grid-template-rows: 100px 100px;
  grid-auto-rows: 25px;
}

enter image description here

body {
  display: grid;
  grid-template-areas:
      "header"
      "footer";
  grid-template-columns: 1fr;
  grid-auto-columns: 100px;
  grid-template-rows: 100px 100px;
  grid-auto-rows: 25px;
}

header {
  grid-area: header;
  background: aqua;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: lightgreen;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

Note: If grid items are placed using grid-template-areas (creating explicit tracks), but they are not sized using grid-template-columns / grid-template-rows, then grid-auto-columns / grid-auto-rows apply to them. (second paragraph)

body {
  display: grid;
  grid-template-areas:
      "header"
      "footer";
  grid-auto-columns: 100px;
  grid-auto-rows: 25px;
}

enter image description here

body {
  display: grid;
  grid-template-areas:
      "header"
      "footer";
  grid-auto-columns: 100px;
  grid-auto-rows: 25px;
}

header {
  grid-area: header;
  background: aqua;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: lightgreen;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

4. The placement of unreferenced grid areas.

Note: To be perfectly honest, I'm about 75% sure this section is completely correct. The spec language was not 100% clear to me. I welcome feedback, corrections, and more accurate answers.

In your code you have a third grid area which is not referenced in grid-template-areas.

body {
  display: grid;
  grid-template-areas: 
    "header"
    "footer";
}

main {
  grid-area: main;
  background: darkorange;
}

Where does grid-area: main go?

As we've already seen, it gets sent into the implicit grid, two columns and two rows in.

enter image description here

The grid area is handled by the grid auto-placement algorithm, which appears to say this:

  1. Because grid-area: main isn't explicitly defined (see section 3 above), it belongs in the implicit grid.

  2. Because grid column line 2 and grid row line 3 (the boundaries of the explicit grid) are named grid lines, new lines must be created in the implicit grid to accommodate the four named lines of grid-area: main. This can only happen with an empty row and empty column in between, separating the explicit grid from the auto-placed implicit grid area.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • seems logical until I try this: https://jsfiddle.net/n7ja89zf/3/ ... following what you said we should not have overlap since the browser will try to avoid having named lines at the same line. – Temani Afif Sep 01 '19 at 19:57
  • And even more surprising: https://jsfiddle.net/n7ja89zf/5/ .. I guess I will not watch my movie tonight. – Temani Afif Sep 01 '19 at 20:03
  • At then end, you were on the good track and your explanation is coorect. I added an answer to better hightlight your points and show more examples about the *implicit lines*. It's a kind of step-by-step explanation until we reach the final result. – Temani Afif Sep 01 '19 at 23:00
  • Interesting; I didn't know implicit grid track corners couldn't touch explicit grid track corners (assuming your last part is correct). Seems like a less-than-ideal design. – TylerH Sep 03 '19 at 13:11
  • @TylerH actually they are touching because the middle empty column is inside the implicit grid. The trick is that an implicit line cannot be defined on the top of another explicit line so we will logically have an implicit track to separate them (the empty column) – Temani Afif Sep 03 '19 at 13:49
  • @TemaniAfif Yes, the overlap is confusing, too. I'm currently reading your answer (though I keep getting distracted...) – TylerH Sep 03 '19 at 13:57
  • I made a small edit to my answer. I wasn't correct when explaining negative numbers. The element will not be placed above but the line will be generated above. The element is always placed below the line for any integer value (negative or positive). – Temani Afif Sep 15 '19 at 20:37
13

This is an extension to what Michael_B already said in order to highlight how the implicit grid lines are created.

Let's start with an easy example:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header;
  background: blue;
}
<div class="container">
  <header>H</header>
</div>

We have a grid item where we only set grid-row-start and the final result is two rows with an empty one. Both within the implicit grid since we didn't define any explicit one.

To understand what is happening let's refer to the specification:

The three properties grid-template-rows, grid-template-columns, and grid-template-areas together define the explicit grid of a grid container. ... If these properties don’t define any explicit tracks the explicit grid still contains one grid line in each axis. ref

So even if we define nothing, we still have an explicit grid with two lines. This is very important because without those line we will not have our empty row.

Now the part explaining grid-row-start:header:

<custom-ident>

First attempt to match the grid area’s edge to a named grid area: if there is a named line with the name ''-start (for grid--start) / -end'' (for grid--end), contributes the first such line to the grid item’s placement.

Otherwise, treat this as if the integer 1 had been specified along with the <custom-ident>.

It's clear that we will fall into the otherwise and will have grid-row-start:header 1:

<integer> && <custom-ident>?

Contributes the Nth grid line to the grid item’s placement...

If a name is given as a <custom-ident>, only lines with that name are counted. If not enough lines with that name exist, all implicit grid lines are assumed to have that name for the purpose of finding this position.

In our case, we don't have enough lines with that name (we don't have any line at all) so we should add at least one line with that name and try to place our element, and since the integer is positive the item will be placed below that line:

enter image description here

The grid has a default line (red one), the use of header generates a new implicit one below it (due to the default value 1 added automatically), and the element will be placed below that line creating an extra row.

If we use -1 will have only one row at the end:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header -1;
  background: blue;
}
<div class="container">
  <header>H</header>
</div>

In this case the implicit line is generated above the explict one and our element placed between both lines.

If a negative integer is given, it instead counts in reverse, starting from the end edge of the explicit grid.

enter image description here

The use of -1 and 1 at the same time will give us the following result:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header -1;
  background: blue;
}

footer {
  grid-row-start: header 1;
  background: red;
}
<div class="container">
  <header>H</header>
  <footer>F</footer>
</div>

enter image description here

Here is another example with multiple items to illustrate that all implicit grid lines are assumed to have that name.

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header 1;
  background: blue;
}
main {
  grid-row-start: main 1;
  background: red;
}
footer {
  grid-row-start: footer -1;
  background: green;
}
extra {
  grid-row-start: extra 5;
  background: orange;
}
<div class="container">
  <header>H</header>
  <main>M</main>
  <footer>F</footer>
  <extra>E</extra>
</div>

In this example we need in total 6 implicit lines, because all the used integers are within the range [-1,5] (excluding 0 which is an invalid value), and, to place each element, all those lines will have the names defined for each element. That's why two elements with the same number will be in the same row (like main and header), since the reference line will be the same even with different names.


Now let's add grid-row-end to our previous example:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header;
  grid-row-end: header;
  background: blue;
}
<div class="container">
  <header>H</header>
</div>

Nothing will happen and will have the exact same result because:

If the start line is equal to the end line, remove the end line. ref

Let's use a different name:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header;
  grid-row-end: foo;
  background: blue;
}
<div class="container">
  <header>H</header>
</div>

Still the same result because both are still equal (yes, they are equal!). Both values will be equal to <name> 1, so both will need only one implicit line. The browser will then create one implicit line that has two different names, thus making both our values equal.

Let's change the value of one:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: header 1;
  grid-row-end: foo 2;
  background: blue;
}
<div class="container">
  <header>H</header>
</div>

Again the same result, but with "different" code. In this case, we will have 2 implicit lines and our element will be placed between them.

Basically the name is not relevant when it comes to implicit grid, because all of them will share the same lines. It's only relevant when we define them inside the explicit grid:

.container {
  width:100px;
  display: inline-grid;

  grid-auto-rows: 40px;
  border: 1px solid;

}
header {
  grid-row-start: hello 1;
  grid-row-end: john 3;
  background: blue;
}
main {
  grid-row-start: main 1;
  grid-row-end: hi 2;
  background: red;
}
footer {
  grid-row-start: footer 2;
  grid-row-end: custom 5;
  background: green;
}
extra {
  grid-row-start: extra 3;
  grid-row-end: fsdfsdfsdfsd 5;
  background: orange;
}
<div class="container">
  <header>H</header>
  <main>M</main>
  <footer>F</footer>
  <extra>E</extra>
</div>

In the above example, you can update the names with any random string and you will always have the same result; it only depends on the integer:

enter image description here

All the above will logically behave the same considering grid-column-*.


Now, we have everything we need to understand what is happening with the initial example.

First we have our explicit grid like below:

body {
  display: grid;
  grid-template-areas: 
     "header"
     "footer";
  /* No relevant but to better illustrate*/
  grid-auto-rows:50px;
  grid-auto-columns:50px;
}

header {
  grid-area: header;
  background: lightblue;
}

/*main {
  grid-area: main;
  background: darkorange;
}*/

footer {
  grid-area: footer;
  background: blue;
}
<header>Header</header>
<!--<main>Main</main>-->
<footer>Footer</footer>

enter image description here

The grid-template-areas property creates implicit named lines from the named grid areas in the template. For each named grid area foo, four implicit named lines are created: two named foo-start, naming the row-start and column-start lines of the named grid area, and two named foo-end, naming the row-end and column-end lines of the named grid area. ref

Now if we add the third element with grid-area:main; it means we have

grid-row-start:main 1;
grid-row-end:main 1;
grid-column-start:main 1;
grid-column-end:main 1;

We remove the *-end because they are equal to *-start

grid-row-start:main 1
grid-column-start:main 1

Based on the previous explanation, we will need an extra implicit line called main and our element will be placed below the horizontal one and at the right of the vertical one:

body {
  display: grid;
  grid-template-areas: 
     "header"
     "footer";
  /* No relevant but to better illustrate*/
  grid-auto-rows:50px;
  grid-auto-columns:50px;
}

header {
  grid-area: header;
  background: lightblue;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: blue;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

enter image description here

If we remove the grid-auto-*, the rows will be the height of their content, making the row between footer-end and main empty. The column will split the width of grid element which is a block element have a full width. That's why you only see an extra column and not the extra row:

body {
  display: grid;
  grid-template-areas: 
     "header"
     "footer";
}

header {
  grid-area: header;
  background: lightblue;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: blue;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>

Another interesting observation is that if you add more elements with grid-area:<name>, they will all sit above each other:

body {
  display: grid;
  grid-template-areas: 
     "header"
     "footer";
}

header {
  grid-area: header;
  background: lightblue;
}

main {
  grid-area: main;
  background: darkorange;
}

footer {
  grid-area: footer;
  background: blue;
}

extra {
  grid-area: extra;
  background: red;
  opacity:0.8;
}
more {
  grid-area: more;
  background: green;
  opacity:0.3;
}
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>
<extra>E</extra>
<more>More</more>

Based on the previous explanation, all of them will have the following:

grid-row-start:<name> 1;
grid-column-start:<name> 1;

Since the number is the same (the name is irrelevant as we already explained) they will all belong to the same area.

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • "Basically the name is not relevant when it comes to implicit grid because all of them will share the same lines. It's only relevant when we define them inside the explicit grid." Indeed, the name is not relevant *anywhere*, since names are just a user-friendly way to refer to the numerical value, which is the value the browser uses during grid creation. Focusing on names is a bit of a red herring. – TylerH Sep 03 '19 at 18:18
  • @TylerH not really *anywhere*. They are relevant inside the explicit grid if you initially define grid-area or grid-line with an explicit name. In this case, the number will be irrelevant and the name will be relevant to find the position. Note in my answer the part that says "First attempt to match .." and if there is no match we will fall into the case where the browser will add the default integer `1`. – Temani Afif Sep 03 '19 at 19:29