Search code examples
csscss-positioncss-grid

Grid places items in the column, not a row


Consider an absolutely positioned <aside> that has a display: grid with grid-template-columns.

I'd expect yellow box appear to the left of the blue box, and both aligned to the right of the container (the black box). Instead, yellow box is placed on top of the blue box.

Also note that adding .fix1 or .fix2 to <aside> makes the grid to lay items in a row, as expected (but break other things).

Why grid items (<span>'s) are placed in a column, not in a row? How to fix this and still position contents using CSS grid? (I'm not interested in Flexbox, floats etc.)

main {
  width: 300px;
}

div {
  position: relative;
}

section {
  width: 100%;
  height: 30px;
  background-color: black;
}
 
aside {
  position: absolute;
  display: grid;
  grid-template-columns: repeat(auto-fill, 2em);
  top: 0;
  right: 5px;
  height: 100%;
}

span {
  display: block;
}

span:nth-child(1) {
  background-color: yellow;
}

span:nth-child(2) {
  background-color: blue;
}

/* Adding this class to <aside>
   fixes the issue, but aligns
   grid contents to the left */
.fix1 {
  width: 100%;
}

/* Adding this class to <aside>
   fixes the issue, but breaks
   the placement of <aside> */
.fix2 {
  position: relative;
}
<main>
  <div>
    <aside class="">
      <span>.</span>
      <span>.</span>
    </aside>
    <section/>
  </div>
</main>


Solution

  • This is not related to CSS grid but to the shrink-to-fit behavior of absolute element. From the specification we have:

    Calculation of the shrink-to-fit width is similar to calculating the width of a table cell using the automatic table layout algorithm. Roughly: calculate the preferred width by formatting the content without breaking lines other than where explicit line breaks occur, and also calculate the preferred minimum width, e.g., by trying all possible line breaks. CSS 2.1 does not define the exact algorithm. Thirdly, calculate the available width: this is found by solving for 'width' after setting 'left' (in case 1) or 'right' (in case 3) to 0.

    Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).

    In your case the available width is big enough and the preferred minimum width is the same as the preferred width (the one used) since there is no possible line break.

    And if we chech the specification of CSS grid related to auto-fill

    When auto-fill is given as the repetition number, if the grid container has a definite size or max size in the relevant axis, then the number of repetitions is the largest possible positive integer that does not cause the grid to overflow its grid container (treating each track as its max track sizing function if that is definite or as its minimum track sizing function otherwise, and taking gap into account); if any number of repetitions would overflow, then 1 repetition. Otherwise, if the grid container has a definite min size in the relevant axis, the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement. Otherwise, the specified track list repeats only once.

    Basically you are falling into the last case because the size of the absolute element is the size of its content and we can only place one repetition inside it.

    Remove the display:grid to see the size:

    main {
      width: 300px;
    }
    
    div {
      position: relative;
    }
    
    section {
      width: 100%;
      height: 30px;
      background-color: black;
    }
     
    aside {
      position: absolute;
      grid-template-columns: repeat(auto-fill, 2em);
      top: 0;
      right: 5px;
      height: 100%;
    }
    
    span {
      display: block;
    }
    
    span:nth-child(1) {
      background-color: yellow;
    }
    
    span:nth-child(2) {
      background-color: blue;
    }
    
    /* Adding this class to <aside>
       fixes the issue, but aligns
       grid contents to the left */
    .fix1 {
      width: 100%;
    }
    
    /* Adding this class to <aside>
       fixes the issue, but breaks
       the placement of <aside> */
    .fix2 {
      position: relative;
    }
    <main>
      <div>
        <aside class="">
          <span>.</span>
          <span>.</span>
        </aside>
        <section/>
      </div>
    </main>

    To obtain what you want you can consider a column flow and define each column to be 2em:

    main {
      width: 300px;
    }
    
    div {
      position: relative;
    }
    
    section {
      width: 100%;
      height: 30px;
      background-color: black;
    }
     
    aside {
      position: absolute;
      display:grid;
      grid-auto-columns: 2em;
      grid-auto-flow:column;
      top: 0;
      right: 5px;
      height: 100%;
    }
    
    span {
      display: block;
    }
    
    span:nth-child(1) {
      background-color: yellow;
    }
    
    span:nth-child(2) {
      background-color: blue;
    }
    
    /* Adding this class to <aside>
       fixes the issue, but aligns
       grid contents to the left */
    .fix1 {
      width: 100%;
    }
    
    /* Adding this class to <aside>
       fixes the issue, but breaks
       the placement of <aside> */
    .fix2 {
      position: relative;
    }
    <main>
      <div>
        <aside class="">
          <span>.</span>
          <span>.</span>
        </aside>
        <section/>
      </div>
    </main>


    position:relative fix the issue because the width calculation will no more be shrink-to-fit but you will have 100% of the container block width ref so you have enough room for many repetition.

    width:100% fix the issue the same way as position:relative because the width will increase to have enough room for more repetition.