csslayout

Trying to understand a weird CSS layout shift


I am self taught and even if I get something to work, I don't like to move on if I don't understand why that something is now working.

I have an inline-block <h1> element and the next element is an inline-block wrapper with six children; three are radio buttons with display: none; and three are labels which are the visible elements. I have styling on the ::checked pseudo-class which slightly changes the labels' size. When the inline-grid element is not wrapped to the next line, I've encountered issues with my layout. When the first of the three children is selected, the layout shifts slightly, but does not when selecting either the second or third child.

My code:

*,
*::after,
*::before {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background-color: rgb(236, 236, 236);
  font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
  min-height: 100vh;
}

h1 {
  color: rgb(113, 0, 179);
  display: inline-block;
  margin-block: 1.5rem;
  margin-inline: 1.25rem 5.5rem;
}

.format-select-wrapper {
  align-items: end;
  display: inline-grid;
  grid-template-columns: 1fr 1fr 1fr;
  margin-bottom: 1rem;
  width: 627px;
}

input[type="radio"] {
  display: none;
}

label {
  background: rgb(113, 0, 179);
  border-radius: 5px;
  box-shadow: 0px 2px 0px black;
  color: whitesmoke;
  cursor: pointer;
  font-weight: 600;
  letter-spacing: 1px;
  margin-inline: auto;
  padding: 1.25rem 2rem;
  text-align: center;
  text-box-edge: cap alphabetic;
  text-box-trim: trim-both;
  user-select: none;
  width: 95%;
}

label:hover,
label:focus-visible {
  background: rgb(84, 44, 102);
}

input[type="radio"]:checked + label {
  background: rgb(158, 0, 250);
  font-size: 0.9rem;
  height: 47px;
  width: 175px;
}
<h1>Music Collection Catalog Lookup</h1>

<div class="format-select-wrapper">
  <input type="radio" name="format" id="btn-records" value="Records" />
  <label for="btn-records">RECORDS</label>
  <input type="radio" name="format" id="btn-tapes" value="Tapes" />
  <label for="btn-tapes">TAPES</label>
  <input type="radio" name="format" id="btn-cds" value="CDs" />
  <label for="btn-cds">CDS</label>
</div>

When I wrapped both elements and made that wrapper display:flex;, everything is fine. I would love to understand what causes the weird layout shift. Finally, is what I did an acceptable solution or am I missing something here?


Solution

  • You're problem is caused by inline. The way inline works when multiple inline text elements are on the same block axis/row is by comparing baselines of text (normally the lowest line that still touches the fontface) and attempting to ensure all baselines are at the same height. This is the root of your problem.

    You can clearly see that when selecting the first label, everything is offset in that inline-grid to make the first label's text's baseline (by default the bottom of the text) the same as the baseline of the h1 element on the same block axis. When selecting the text elements after that first child, the inline-grid is offset to make the first label's text's baseline the same as the baseline of the h1 element yet again. At this point it may seem strange that the other label's text's baseline are not also set to be the same height as everything else on that block axis. I do not know exactly why this happens, but I am guessing that the inline positioning algorithm only takes into consideration the first text element of a wrapper object and after that the grid positioning algorithm takes over inside the wrapper object making the rest of the text's baselines different heights from the rest of the box axis.

    This effect can be seen even more when adding another text element to the mix with height: 4em;. The baseline of all of the text elements are put to the same height even though doing so requires making this new text element's content box bottom go super low on the screen.


    Proposed Solution

    Use a wrapper with display: flex; flex-wrap: wrap; instead of using inline to make by-default block elements go on the same row. I have rewritten your code with these changes below.

    *,
    *::after,
    *::before {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    body {
      background-color: rgb(236, 236, 236);
      font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
      min-height: 100vh;
    }
    
    .form-wrapper {
      display: flex;
      flex-wrap: wrap;
    }
    
    h1 {
      color: rgb(113, 0, 179);
      margin-block: 1.5rem;
      margin-inline: 1.25rem 5.5rem;
    }
    
    .format-select-wrapper {
      align-items: end;
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      margin-bottom: 1rem;
      width: 627px;
    }
    
    input[type="radio"] {
      display: none;
    }
    
    label {
      background: rgb(113, 0, 179);
      border-radius: 5px;
      box-shadow: 0px 2px 0px black;
      color: whitesmoke;
      cursor: pointer;
      font-weight: 600;
      letter-spacing: 1px;
      margin-inline: auto;
      padding: 1.25rem 2rem;
      text-align: center;
      text-box-edge: cap alphabetic;
      text-box-trim: trim-both;
      user-select: none;
      width: 95%;
    }
    
    label:hover,
    label:focus-visible {
      background: rgb(84, 44, 102);
    }
    
    input[type="radio"]:checked + label {
      background: rgb(158, 0, 250);
      font-size: 0.9rem;
      height: 47px;
      width: 175px;
    }
    <div class="form-wrapper">
    <h1>Music Collection Catalog Lookup</h1>
    <div class="format-select-wrapper">
      <input type="radio" name="format" id="btn-records" value="Records" />
      <label for="btn-records">RECORDS</label>
      <input type="radio" name="format" id="btn-tapes" value="Tapes" />
      <label for="btn-tapes">TAPES</label>
      <input type="radio" name="format" id="btn-cds" value="CDs" />
      <label for="btn-cds">CDS</label>
    </div>
    <div>

    More Reading

    1. https://www.w3.org/TR/css-inline-3/
    2. https://www.w3.org/TR/css-grid-1/
    3. https://www.w3.org/TR/css-flexbox-1/