5

So I am defining a layout in CSS Grid which needs this particular row to have identically sized inputs. I can do the math and just specify percentages and it works fine (see commented out line) but when I specify it via the fractional unit (fr) then the spanned column "squishes" more than the non-spanned columns.

.A {
  grid-area: a;
}

.B {
  grid-area: b;
}

.C {
  grid-area: c;
}

.Exchange_Row {
  display: grid;
  grid-template-columns: 1fr 9fr 10fr 10fr 1fr;
  /*
   grid-template-columns:  3% 27% 30% 30% 3%;
  */
  grid-template-areas: "a a b c ."
}

input[type=text] {
  border: solid;
}
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>

When I resize this to a narrow view (narrower than 598) then A continues to get smaller (down to 127px) but B and C stay the same at 169px.

If I comment out the grid-template-columns line using fr units and instead use the percentages below then it works fine.

Here are screenshots showing the different sizes when the columns are defined using fr units (as in the code above):

A with size 127px

B with size 169px

C with size 169px

Can anybody help me understand why this is happening? I expected A to stop at 169px or else B and C to continue to shrink down to 127px since they are all defined almost identically.

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Peter Bowers
  • 3,063
  • 1
  • 10
  • 18

1 Answers1

4

Add min-width:0 to input and you should fix the percentage value so they are exacltly the same as the fr. You have 31fr so 1fr is about 100/31 = 3.225%

.A {
  grid-area: a;
}

.B {
  grid-area: b;
}

.C {
  grid-area: c;
}

.Exchange_Row {
  display: grid;
  grid-template-columns: 1fr 9fr 10fr 10fr 1fr;
  grid-template-areas: "a a b c ."
}
.Exchange_Row.percentage {
 grid-template-columns:  3.225% 29.03% 32.25% 32.25% 3.225%;
}

input[type=text] {
  border: solid;
  min-width:0;
}
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row percentage">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>

This is related to Automatic Minimum Size of Grid Items and how grid items are sized. An input element has a default width set by browser considered as a minimum size (it's also the same with flexbox). This size is used to define the column size of the 10fr.

If we refer to the above links:

Once the grid items have been placed, the sizes of the grid tracks (rows and columns) are calculated, accounting for the sizes of their contents and/or available space as specified in the grid definition.

Both columns (defined by 10fr) will be sized considering their content (the input element) but the first two columns (1fr and 9fr) cannot be sized using the input because this one is spaning both of them and not only one of them. To say this differently: the 1fr and 9fr columns don't have any explicit content thus they will be sized according to the available space then the input will match this space.

In other words, the first input is sized based on the 1fr and 9fr but the other input is sizing the 10fr.

By adding min-width:0 we remove the Automatic Minimum size constraint thus there is no more content size to account for and all the grid columns will get sized using the available space then the inputs will match that size.

Adding width:100% will also fix the issue but differently. In this case, we tell the input to have its width based on its containing block (the grid item) so we need to first define the size of the grid item (considering the available space) then resolve the percentage value to define the input size.

This will happen with any configuration even if you change the fr values:

.A {
  grid-area: a;
}

.B {
  grid-area: b;
}

.C {
  grid-area: c;
}

.Exchange_Row {
  display: grid;
  grid-template-columns: 5fr 5fr 1fr 1fr 1fr;
  grid-template-areas: "a a b c ."
}
.Exchange_Row.another {
 grid-template-columns:  50fr 50fr 3fr 1fr 1fr;
}

input[type=text] {
  border: solid;
}
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row another">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>

Even if we define bigger value for the first input, the other will always win because they will define the size of their own grid column and the first one will simply take the remaining.

UPDATE

Another way to explain this (as comment by @Michael_B) is that 1fr unit is equivalent to minmax(auto,1fr) which means that for the 10fr column we have the auto size of the content as a lower bound which isn't the case for the two first columns since they don't have the input as their content.

We can use minmax(0,1fr) to overcome this instead of using min-width:0

.A {
  grid-area: a;
}

.B {
  grid-area: b;
}

.C {
  grid-area: c;
}

.Exchange_Row {
  display: grid;
  grid-template-columns: minmax(0,1fr) minmax(0,9fr) minmax(0,10fr) minmax(0,10fr) minmax(0,1fr);
  grid-template-areas: "a a b c ."
}
.Exchange_Row.percentage {
 grid-template-columns:  3.225% 29.03% 32.25% 32.25% 3.225%;
}

input[type=text] {
  border: solid;
}
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>
<div style="width: 90%; border: solid; border-radius: 5px; padding: 5px;">
  <div id="currencyRow" class="Exchange_Row percenage">
    <input type="text" class="A" value="A" />
    <input type="text" class="B" value="B" />
    <input type="text" class="C" value="C" />
  </div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Thanks for your input. I'm not trying to get the percentage columns and the fr columns to look the same - I'm trying to get the fr columns to remain equal in width since they all have 10fr width. The min-width does solve the problem, as does width=100%, but it's a workaround rather than an actual answer to the question. I'm trying to understand why it's happening - seems like it might be a bug in the implementation of CSS grid... – Peter Bowers Jan 10 '19 at 14:36
  • @PeterBowers I am pretty sure it's not a bug, it's by design. I will add it to my answer when I find the accurate explanation (I am searching for it) – Temani Afif Jan 10 '19 at 14:46
  • @PeterBowers I added an explanation but I am not sure at 100% of it. It's logical but maybe I missing few things. – Temani Afif Jan 10 '19 at 16:00
  • 1
    When you use percentages for sizing the columns, you're actually setting their lengths. That's a direct way of setting column widths. On the other hand, when you use `fr` units, you are not setting any lengths. You're only distributing free space (if any). So the content of a grid item will have a different impact on track sizing set by `%` and `fr`. – Michael Benjamin Jan 10 '19 at 23:33
  • 4
    It's also important to note that `1fr`, by default, means [`minmax(auto, 1fr)`](https://stackoverflow.com/q/52861086/3597276). That means that the minimum width of a column using `fr` units is the width of the content. Percentage sizing doesn't have that min-width limitation. To get around that, use `minmax(0, 1fr)` instead ([demo](https://jsfiddle.net/ef6kbgau/1/)). – Michael Benjamin Jan 10 '19 at 23:35
  • @Michael_B I agree about all this but the point here is the fact that the first input is spaning two columns. It's clear that the middle columns are defined by the input size since it's their content but it's not the case for the first two ones because they don't really have a content as we cannot say that the input is a content of one of them thus they are first sized with the remaining space then the input is filling that space (like I explained) unlike the other that are sized based on the input size. Is this correct so? – Temani Afif Jan 10 '19 at 23:41
  • Yes, that sounds right to me, which is why I didn't post a separate answer. – Michael Benjamin Jan 10 '19 at 23:42
  • 2
    @Michael_B ok perfect, edited the answer to add your comments too – Temani Afif Jan 10 '19 at 23:52
  • 1
    Great - with the edit that makes perfect sense. So the "10fr" columns have an implicit min-width while the "1fr 9fr" column doesn't really have clear content and so doesn't have that same implicit min-width. Makes sense. THANKS, ALL! – Peter Bowers Jan 11 '19 at 11:50