Search code examples
htmlcssformsword-wrap

CSS: right label with float left wraps under zoom


I have a form with legend and fieldsets, and need to have labels by left side of inputs.

I want to stylish my form labels with CSS only, but CSS selectors cannot find preceding siblings, so I need to write label after input in HTML code in order to select the labels connected with invalid inputs, and use float: left.

When input is filled incorrectly, corresponding label needs to be changed. In following examples the label becomes red and validation fail description message unfolds.

Description label needs to be on the same line as corresponding input (for aesthetic purposes) or, at least, left corner should be aligned by the most right point of the input.

The problem occurs when I try to zoom in. I emulate this by setting lower width to the outer div.

#container {
  width: 200px;
}

form {
  border: 1px solid red;
  overflow: auto;
}

fieldset {}

section {}

input[type="text"] {
  display: inline-block;
}

input[type="text"]+label {
  display: inline-block;
  float: left;
  text-align: right;
  width: 80px;
}

input[type="text"]:invalid+label {
  color: red;
}

input[type="text"]+label+label {
  display: none;
}

input[type="text"]:invalid+label+label {
  display: inline-block;
  color: red;
}

.field-row {
  display: inline-block;
  white-space: nowrap;
}
<div id="container">
  <form>
    <fieldset>
      <legend>Fieldset</legend>
      <section>
        <div>
          <div class="field-row">
            <input id="first" type="text" value="" required />
            <label for="first">First</label>
            <label>
             Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
            </label>
          </div>
        </div>
        <div>
          <div class="field-row">
            <input id="second" type="text" value="" />
            <label for="second">Second</label>
          </div>
        </div>
      </section>
    </fieldset>
  </form>

On this snippet you can see that the first row is very wide and goes out of outer fieldset (at least in Google Chrome). If I use overflow: auto for the fieldset, legend text moves left when I move scrollbar.

When required input is valid, corresponding label goes to another row, but I need the label to be on one line with connected input.

The hack, which helped me, is to set min-width of the field-row block wider than label + input width. Though, this needs to know maximum allowed width of them and either use JavaScript or change it on any padding, margin and width changes of dependent fields.

Also it works well when I don't use float. Although, this approach will need to use JavaScript to change change label in input validation state changed.

I make a ReactJS application, so JavaScript is not a problem, but I want to investigate and use CSS capabilities as much as I can.


Solution

  • If you only need to support modern browsers, I recommend giving up on trying to use float for this and use flexbox instead.

    All you need to do is to make the .field-row div display: flex; and then set the order property for the input and label elements. Like this:

    #container {
      width: 200px;
    }
    
    form {
      border: 1px solid red;
      overflow: auto;
    }
    
    fieldset {}
    
    section {}
    
    input[type="text"] {
      order: 2;
    }
    
    input[type="text"]+label {
      text-align: right;
      order: 1;
      width: 80px;
    }
    
    input[type="text"]:invalid+label {
      color: red;
    }
    
    input[type="text"]+label+label {
      display: none;
      order: 3;
    }
    
    input[type="text"]:invalid+label+label {
      color: red;
      display: inline;
    }
    
    .field-row {
      display: flex;
      white-space: nowrap;
    }
    <div id="container">
      <form>
        <fieldset>
          <legend>Fieldset</legend>
          <section>
            <div>
              <div class="field-row">
                <input id="first" type="text" value="" required />
                <label for="first">First</label>
                <label>
                 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
                </label>
              </div>
            </div>
            <div>
              <div class="field-row">
                <input id="second" type="text" value="" />
                <label for="second">Second</label>
              </div>
            </div>
          </section>
        </fieldset>
      </form>
    </div>