HTML & CSS - - By Craig Buckler

Make Forms Fun with Flexbox

Curious about deep-diving into Flexbox? Check out our Master CSS Layouts with Flexbox course by front-end dev and General Assembly London teacher Guy Routledge. It’s available for all SitePoint members. Watch our sample video from the course below.

Loading the player…

I’ll come clean: HTML forms are rarely fun – but they’re a necessary part of web development. Fortunately, some historical difficulties can be alleviated with CSS flexbox.

Consider how forms are typically coded without flexbox. What HTML mark-up would you use for the following fields?

Final example of our form

We would normally use mark-up as such:

<div>
  <label for="name">name</label>
  <input id="name" name="name" type="text" />
</div>

<div>
  <label for="experience">experience</label>
  <select id="experience" name="experience"><!-- options --></select>
</div>

<div>
  <input id="html" name="html" type="checkbox" />
  <label for="html">HTML</label>
</div>

<div>
  <input id="css" name="css" type="checkbox" />
  <label for="css">CSS</label>
</div>

<div>
  <input id="javascript" name="javascript" type="checkbox" />
  <label for="javascript">JavaScript</label>
</div>

A number of issues arise as you begin to apply CSS styling:

  1. The HTML source order of the label and field is inconsistent and depends on the type. The label typically comes before the field but is better after checkboxes and radio buttons. You must re-order your mark-up if you change the type.
  2. Consistently aligning and sizing inputs, textareas and select boxes can be difficult. Most have default styles which can be awkward to change in some browsers.
  3. Aligning a label next to its field requires trial and error to get the positioning correct.
  4. It’s (currently) impossible to style a label according to the field’s activation or content state when it appears first.

Flexbox can solve these issues. We can use clean and consistent HTML where the label is placed after every field and it’s not necessary to add helper CSS classes.

What’s Flexbox?

Flexbox is a magical way to layout multiple elements on the page. They can be aligned in horizontally, vertically, in order, reversed and sized in relation to other elements. Flexbox can be confusing and, even after using them for months, you’ll need to consult documentation now and again. However, the most basic CSS to enable flexbox:

.container {
  display: flex;
}

That’s it. All child elements of the .container are now flexbox items which, by default, will size themselves into a single row.

The main difference between flexbox and grid systems (such as the new CSS Grid Module) is that flexbox is one-dimensional. Flexbox items are laid in a line and wrap when necessary. Grids are two-dimensional with defined columns and non-wrapping rows.

Flexbox is less powerful than a grid but it’s ideal for laying out smaller components such as navigation menus, lists and form fields.

Flexbox tools and resources:

Why no Classes?

Those who have adopted CSS philosophies such as SMACSS or BEM will be horrified by the dearth of class definitions in this article. The HTML and tag-targetting CSS is intended to keep the code clean and aid understanding without distraction.

You could consider the code as a base style for all forms on your site – presuming you have a consistent layout throughout. You can add classes should you want to use but they may not necessary for smaller sites or demonstrations.

Flexboxing the Form

The code for the image above can be seen in action here:

See the Pen Flexboxed Forms by SitePoint (@SitePoint) on CodePen.

Examine the HTML and you’ll notice that label elements are always defined after the field regardless of the type, e.g.

<div>
  <input id="name" name="name" type="text" />
  <label for="name">name</label>
</div>

<div>
  <select id="experience" name="experience"><!-- options --></select>
  <label for="experience">experience</label>
</div>

<div>
  <input id="html" name="html" type="checkbox" />
  <label for="html">HTML</label>
</div>

Each <div> is set as a flexbox container and align-items: center ensures labels and fields align vertically:

fieldset div {
  display: flex;
  align-items: center;
}

The field and the label are now flexbox items but, in most cases, they’re in the wrong order because we want the label first:

Basic flexbox styles

(This is how the form appears on older browsers such as IE9 and below)

We can fix ordering using the appropriately-named order property which is set to a positive or negative integer. The lower the number, the “sooner” it is displayed on the page:

label {
  order: 1;
  width: 10em;
  padding-right: 0.5em;
}

input, textarea, select {
  order: 2;
  flex: 1 1 auto;
}

We apply a width and padding to the label to ensure they align consistently. One disadvantage of flexbox is that you may need to adjust this width if you introduce longer text labels.

Note the flex: 1 1 auto setting for input, textarea and select fields. flex is a shorthand property which specifies how the item can alter its dimensions to fill the available space. The three values are:

  • flex-grow – the amount of space a flex item can grow in relation to others, e.g. a value of 2 would permit the item to be twice the width of any item with a value of 1.
  • flex-shrink – similarly, the amount of space a flex item can shrink in relation to others.
  • flex-basis – the initial width of a flex item.

flex: 1 1 auto essentially says: use whatever space remains. Our field elements are sized the same and there’s no need to mess around with paddings and borders!

Initial label and field styles

Unfortunately, our checkbox fields are now styled incorrectly (yes, that wouldn’t have happened if we used class names!) but we can target checkboxes and radio fields directly to fix that. The following code places the field first again, stops it using all the space and applies a margin which matches our label width set above:

input[type="checkbox"], input[type="radio"] {
  order: 1;
  flex: none;
  width: auto;
  margin-left: 10em;
}

We can also target the associated labels using a sibling selector (~ or + is fine) because they come after the field in our HTML source. We can switch off the default width and apply a little padding:

input[type="checkbox"] ~ label, input[type="radio"] ~ label {
  width: auto;
  padding-left: 0.4em;
}

Sprinkle on a few colors and styles and we have a glorious form which will look great no matter what fields we throw at it.

Final example of our form

Label Style

Another advantage of the label coming last is that we can style it according to the state of its field. For example, set the label to red when the field has focus:

input:focus ~ label, textarea:focus ~ label, select:focus ~ label {
  color: #933;
}

or make the label bold when a checkbox or radio button is checked:

input:checked ~ label {
  font-weight: bold;
}

An enhanced version of our form

Add a few CSS transitions and you can make labels to resize, move, fade or have any other animated effect.

Flexbox Browser Support

Flexbox is supported in all current mainstream browsers. IE11 has several flexbox issues but they don’t arise in the simple properties we’re using for this example. IE10 requires -ms- prefixes but will work.

Older browsers display all fields before the label but the tab ordering remains correct and it is obvious which field you’re using.

My only hesitation is screen readers. Confusion could arise if a reader relies on the HTML source order rather than the associated label text. I’m not aware of any specific issues but please let me know if you encounter one!

Finally, if this encourages you to apply flexbox liberally over your forms, be aware that display: flex cannot be applied to <fieldset> elements owing to bugs in Chrome and Firefox. Hope that tip saves you some of the hours I wasted!