cssflexboxaspect-ratio

Flexbox issue when children are buttons using aspect-ratio


I’m using flex in a number input component to render custom “+” and “-” buttons next to the input itself. The buttons use aspect-ratio: 1/1; to ensure they are always square, but I can’t seem to get the buttons’ container to honor that. Instead, the buttons are overflowing the container, almost as if the container is measuring their size without the aspect-ratio property. When I remove that property the container renders at the appropriate size.

Some example code:

.input-interior {
    box-shadow: 0 1px 0 blue;
    box-sizing: border-box;
    display: flex;
    flex-grow: 1;
    position: relative;
    width: 600px;
}

.input-container {
    flex-grow: 1;
    min-width: 0;
}

input {
    appearance: textfield;
    background-color: transparent;
    border: none;
    box-shadow: none;
    box-sizing: border-box;
    color: rgb(15 15 15);
    height: 80px;
    margin: 0;
    outline: none;
    padding-left: 8px;
    padding-right: 8px;
    padding-top: 5px;
    padding: 0;
    width: 100%;
}

.input-actions {
    box-shadow: 0 0 0 1px green;
    box-sizing: border-box;
    column-gap: 8px;
    display: flex;
    flex-shrink: 0;
    padding: 8px;
}

button {
    border: none;
    margin: 0;
    padding: 0;
    aspect-ratio: 1 / 1;
    min-height: 44px;
    align-items: center;
    display: flex;
    justify-content: center;
    min-width: auto;
    width: auto;
}
<div class="input-interior">
    <div class="input-container">
        <input min="0" step="1" type="number" value="0" />
    </div>
    <div class="input-actions">
        <button type="button">-</button>
        <button type="button">+</button>
    </div>
</div>

How do I get the .input-actions container to honor the dynamic size of its children?


Solution

  • Short answer: the min-height should be equal to a whole available height.

    button {
        min-height: 64px;
    }
    

    .input-interior {
      box-shadow: 0 1px 0 blue;
      box-sizing: border-box;
      display: flex;
      flex-grow: 1;
      position: relative;
      width: 600px;
    }
    
    .input-container {
      flex-grow: 1;
      min-width: 0;
    }
    
    input {
      appearance: textfield;
      background-color: transparent;
      border: none;
      box-shadow: none;
      box-sizing: border-box;
      color: rgb(15 15 15);
      height: 80px;
      margin: 0;
      outline: none;
      padding-left: 8px;
      padding-right: 8px;
      padding-top: 5px;
      padding: 0;
      width: 100%;
    }
    
    .input-actions {
      box-shadow: 0 0 0 1px green;
      box-sizing: border-box;
      column-gap: 8px;
      display: flex;
      flex-shrink: 0;
      padding: 8px;
    }
    
    button {
      border: none;
      margin: 0;
      padding: 0;
      aspect-ratio: 1 / 1;
      min-height: 64px;
      align-items: center;
      display: flex;
      justify-content: center;
      min-width: auto;
      width: auto;
    }
    <div class="input-interior">
      <div class="input-container">
        <input min="0" step="1" type="number" value="0" />
      </div>
      <div class="input-actions">
        <button type="button">-</button>
        <button type="button">+</button>
      </div>
    </div>

    Details

    When there is only min-height the .input-actions container uses it also as a width of the buttons (since aspect-ratio: 1/1) and bases its own width on that:

    112px = (2 * button min-height 44px) + (2 * container padding 8px) + (column-gap 8px)
    

    Seems reasonable to set min-height: 100%, but in that case container will become even smaller than 40px (depending on a font metrics) because it needs an absolute units, not relative.

    A CSS variable and calc() can be used to make the height settable in one place:

    .input-interior {
      --input-height: 80px;
      box-shadow: 0 1px 0 blue;
      box-sizing: border-box;
      display: flex;
      flex-grow: 1;
      position: relative;
      width: 600px;
    }
    
    .input-container {
      flex-grow: 1;
      min-width: 0;
    }
    
    input {
      appearance: textfield;
      background-color: transparent;
      border: none;
      box-shadow: none;
      box-sizing: border-box;
      color: rgb(15 15 15);
      height: var(--input-height);
      margin: 0;
      outline: none;
      padding-left: 8px;
      padding-right: 8px;
      padding-top: 5px;
      padding: 0;
      width: 100%;
    }
    
    .input-actions {
      box-shadow: 0 0 0 1px green;
      box-sizing: border-box;
      column-gap: 8px;
      display: flex;
      flex-shrink: 0;
      padding: 8px;
    }
    
    button {
      border: none;
      margin: 0;
      padding: 0;
      aspect-ratio: 1 / 1;
      min-height: calc(var(--input-height) - 8px * 2);
      align-items: center;
      display: flex;
      justify-content: center;
      min-width: auto;
      width: auto;
    }
    <div class="input-interior">
      <div class="input-container">
        <input min="0" step="1" type="number" value="0" />
      </div>
      <div class="input-actions">
        <button type="button">-</button>
        <button type="button">+</button>
      </div>
    </div>