27

Without the use of tables or display:table, is it possible to have a flexible width area for labels to the left of inputs? I would also like to avoid grid layout for browser compatibility reasons. I'd like css to take care of this:

short_label   input_box____________________
tiny_label    input_box____________________
medium_label  input_box____________________

And then also handle larger labels accordingly:

short_label            input_box__________
medium_label           input_box__________
very_extra_long_label  input_box__________

But I do not want:

short_label            input_box__________
tiny_label             input_box__________
medium_label           input_box__________

So the first column needs to have a flexible width, and the second column needs to grow to fill space. My html would ideally look something like this, but if necessary, "row" divs can be added. I feel like there is a flex answer, but maybe not since all the rows needs to be aligned.

<div class='aligned_form'>

  <label for='a'>short_label</label>
  <input type='text' id='a'>

  <label for='b'>medium_label</label>
  <input type='text' id='b'>

  <label for='c'>very_extra_long_label</label>
  <input type='text' id='c'>

</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Michael Arrison
  • 1,464
  • 2
  • 13
  • 22
  • Per the Docs (https://v4-alpha.getbootstrap.com/components/forms/#using-the-grid) you should be using the Grid for this. What browser compatibility concerns prevent you from using the Grid but still allows for using Bootstrap v4 at all? – Robert Jul 20 '17 at 20:55
  • The bootstrap grid is too rigid for my needs. I need the first column to be of flexible width. I can't just say col-3 and col-9. I meant I don't want to use display:grid for browser compatibility. If there is a bootstrap grid solution, I'm open to it. – Michael Arrison Jul 20 '17 at 20:58
  • 1
    Why don't you just use a table? You are clearly looking for the behavior of a table, so I wouldn't call it bad practice. – Eliran Pe'er Jul 23 '17 at 12:23

3 Answers3

16

align-items: stretch

Flexbox has a feature commonly known as "equal height columns". This feature enables flex items in the same container to all be equal height.

This feature comes from two initial settings:

  • flex-direction: row
  • align-items: stretch

With a slight modification, this feature can become "equal width rows".

  • flex-direction: column
  • align-items: stretch

Now a column of flex items will have the width of the longest item.

Reference:


align-content: stretch

An initial setting on a flex container is align-content: stretch. This setting will distribute rows or columns (depending on flex-direction) across the length of the container.

In this case, in order to pack two columns to the start of the container, we need to override the default with align-content: flex-start.

References:


flex-direction: column, flex-wrap: wrap and height

Since you have a preferred HTML structure (with all form elements logically ordered in one container), flex items will need to wrap in order to form a column of labels and a column of inputs.

So we need to override flex-wrap: nowrap (the default) with wrap.

Also, the container must have a fixed height so that items know where to wrap.

References:


The order property

The order property is needed to properly align labels and inputs across columns.

Reference:


.aligned_form {
  display: flex;
  flex-flow: column wrap;
  align-content: flex-start;
  height: 75px;
}

label[for='a'] { order: -3; }
label[for='b'] { order: -2; }
label[for='c'] { order: -1; }

label, input {
  height: 25px;
  padding: 5px;
  box-sizing: border-box;
}
<!-- No changes to the HTML. -->

<div class='aligned_form'>

  <label for='a'>short_label</label>
  <input type='text' id='a' placeholder="short_label">

  <label for='b'>medium_label</label>
  <input type='text' id='b' placeholder="medium_label">

  <label for='c'>very_extra_long_label</label>
  <input type='text' id='c' placeholder="very_extra_long_label">

</div>

jsFiddle demo 1


Changing the HTML Structure

If you can change the HTML, here's an alternative solution.

  • One primary flex container with two flex item columns (labels and inputs)
  • Add flex: 1 to the inputs column so that it consumes all free space in the row and packs the labels column to the width of its longest item

form {
  display: flex;
}

form > div {
  display: flex;
  flex-direction: column;
}

form > div:last-child {
  flex: 1;
}

label, input {
  height: 25px;
  padding: 5px;
  box-sizing: border-box;
}
<form>
  <div>
    <label for='a'>short_label</label>
    <label for='b'>medium_label</label>
    <label for='c'>very_extra_long_label</label>
  </div>
  <div>
    <input type='text' id='a' placeholder="short_label">
    <input type='text' id='b' placeholder="medium_label">
    <input type='text' id='c' placeholder="very_extra_long_label">
  </div>
</form>

jsFiddle demo 2

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • 1
    Thanks @Michael_B. Your first solution is unmaintainable since it manually sets the height of the container and would have to change every time an input was added or taken away. However, your second solution works nicely. I don't like the idea of breaking up the label from its input, but it'll work. Answer accepted. – Michael Arrison Jul 24 '17 at 13:16
9

I know you said you don't want CSS Grid layout, but I'm going to add it here for documentation purposes. As I think it's the cleanest of the solutions if you don't mind the support problems.

You can always set a fixed width for the inputs by replacing 1fr with a pixel or other value. Also align-self is centering the label and inputs vertically, if you'd prefer bottom aligned change center to end. This keeps the HTML super clean too!

.container{
  display: grid;
  grid-gap: 10px;
  grid-template-columns: [label] auto [input] 1fr;
}
input {
  grid-column: input;
  align-self: center;
}
label {
  grid-column: label;
  align-self: center;
}
<div class="container">
      <label for='a'>short_label</label>
      <input type='text' id='a'>

      <label for='b'>medium_label</label>
      <input type='text' id='b'>

      <label for='c'>very_extra_long_label</label>
      <input type='text' id='c'>
</div>
Don
  • 3,987
  • 15
  • 32
4

I think @Michael_B's solution is great, but another way would be to create a flex row with 2 columns, then make each column a flex column. Just set a height on the label/inputs so they're consistent, and adjust the margins.

<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>

<style>
label,input {
  height: 25px;
  margin:0 0 .5em;
}
label {
  margin-right: .5em;
}
</style>

<div class="container">
  <div class="row">
    <div class="d-flex flex-column">
      <label for='a'>short_label</label>
      <label for='b'>medium_label</label>
      <label for='c'>very_extra_long_label</label>
    </div>
    <div class="d-flex flex-column">
      <input type='text' id='a'>
      <input type='text' id='b'>
      <input type='text' id='c'>
    </div>
  </div>
</div>
Michael Coker
  • 52,626
  • 5
  • 64
  • 64