Infield Form Labels
I’ve done enough web development now and taught enough web development courses that I am starting to develop opinions. Or maybe I’ve had opinions since the start, but now I feel qualified enough to air them publicly. Today’s opinion is on labels for form elements. Let’s all agree that we need them. But where do we put them?
We could go with labels on the left and inputs on the right—with a grid to keep everything aligned. But the labels will have various lengths. Do we right-align them against the inputs, leaving a ragged axis on the left? Or do we left-align them, leaving internal space between the shortest labels and the inputs?
Or we could go with labels and inputs all in one vertical stack. The danger with this arrangement is ambigous visual parsing. Does a label associate with the input above it or below it?
I think neither approach is good. Both suffer from separateness. The label and input are distinct elements. Why not just put the label and the input in the same box? That’s easy enough to do in HTML.
<label class="infield">
<span>Name</span>
<input type="text">
</label>
<label class="infield">
<span>Regrets</span>
<input type="text">
</label>
<label class="infield">
<span>Birth Order</span>
<input type="text" class="number-input">
</label>
But these label-input pairs are very much separated when this HTML is rendered. We can hide this separateness with some CSS that visually blends the label
, span
, and input
together. We strip out all of the input
decorations and make the label
look like one big box that we can type in. Really the label
is just a vertical stack of its two children.
.infield {
margin-top: 10px;
display: flex;
flex-direction: column;
padding: 5px;
border: 1px solid darkgray;
border-radius: 3px;
outline-width: 3px;
outline-offset: -2px;
background-color: #F6F6F6;
box-shadow: inset 0 0 2px #999999;
}
.infield > span {
color: darkgray;
font-family: sans-serif;
font-size: 12px;
margin-bottom: 5px;
}
.infield > input {
border: none;
outline: none;
background: none;
font-size: 16px;
}
We end up with the form shown in Figure 1.
What about our outline? Usually the browser shows us what form element we are focused on. Well, if we have :focus-within
, we can apply the outline to the surrounding label
.
.infield:focus-within {
outline-color: blue;
outline-style: auto;
}
.infield:focus-within > span {
font-weight: bold;
color: blue;
}
The form shown in Figure 2 is more like what we are used to.
Perhaps we could highlight the fields when something has been entered, or when something has been entered incorrectly. Let’s add some styles for these two states.
.infield:focus-within > span, .infield.non-empty > span {
color: blue;
font-weight: bold;
}
.infield.error {
outline-color: red;
outline-style: auto;
background-color: #FFEEEE;
}
.infield.error > span {
color: red;
font-weight: bold;
}
Some JavaScript is needed to dynamically add or remove these classes when the user types in the boxes.
const infields = document.querySelectorAll('.infield');
for (let infield of infields) {
const input = infield.children[1];
input.addEventListener('input', () => {
infield.classList.toggle('non-empty', input.value.length > 0);
});
if (input.classList.contains('number-input')) {
input.addEventListener('input', () => {
infield.classList.toggle('error', input.value.length > 0 && !input.value.match(new RegExp('^\\d+$')));
});
}
}
There we have it! A form that is clearly labeled and easy to align. All that’s left is to put a really big Submit button below it, as we see in Figure 4.