loading...
Cover image for Use more HTML Elements - Floating Form Field Label

Use more HTML Elements - Floating Form Field Label

icyjoseph profile image Joseph ・4 min read

I read this article, which motivated to build the floating form field using two seemingly forgotten HTML elements:

Expectation

Floating Label Example

Implementation

The MDN page for the legend already shows it doing the floating label trick, at least at the final frame, in combination with a fieldset element.

<fieldset>
  <input type="text" id="my-input" placeholder="my input"/>
  <legend>
    <label for="my-input">my input</label>
  </legend>
</fieldset>

What we want to do is:

  • Translate the legend to align with the input::placeholder.
  • When the input::placeholder is not shown, translate the legend up.

Consider:

  • You can translate the label all the way up, above the input, for this example I align it with the top-border of the input field.
  • You can also remove the padding added to the left on the legend, or translate it on the X axis when the placeholder is gone.

If you are going to implement this in an application it is better to use proper class names to avoid extremely specific selectors, here I just style the HTML elements.

First style the fieldset element:

  • Declare a CSS var named --typography-size, set to 14px.
  • Declare a CSS var named --base-ratio, set to 4px.
  • Set font-family to monospace.
  • Set padding to 0
  • Set border to none.

The margin in this case is optional, and set for the demo only.

fieldset {
  --typography-size: 14px;
  --base-ratio: 4px;
  font-family: monospace;
  padding: 0;
  border: none;
  margin: 16px;
}

Second, the input element.

  • Set the font-size to the --typography-size variable.
  • The input field does not inherit to the root element font-family by default, so do so.
  • Declare a border of 1px with solid style line, and color #c3c3c3.
  • Set margin to 0.
  • Set padding to 0.
  • To create spacing between the top border and actual input text, defined padding-top, in this case, 3 times the --base-ratio.
  • Similarly create space at the bottom, in this case, 2 times the --base-ratio.
  • Last but not least, space the text content from the left setting padding-left to 3 times the --base-ratio.
input {
  font-size: var(--typography-size);
  font-family: inherit;
  border: 1px solid #c3c3c3;
  margin: 0;
  padding: 0;
  padding-top: calc(var(--base-ratio) * 3);
  padding-bottom: calc(var(--base-ratio) * 2);
  padding-left: calc(var(--base-ratio) * 3);
}

It is very important to realize that for the legend, the input text is actually 13px from the left. That is because the border has 1px width. And in general:

3 * var(--base-ratio) + 1px

You could set the input::placeholder to opacity:0 (in the Codepen above it is done like that) but if you want to gracefully hide it when the input gets focused/unfocused do this:

input::placeholder {
  opacity: 1;
  transition: opacity 0.75s ease;
}

input:focus::placeholder {
  opacity: 0;
  transition: opacity 0.75s ease;
}

Third the legend element:

  • Match the font-size, using the --typography-size variable.
  • To align the legend with the input::placeholder text we need to account for 13px or rather 3 times the --base-ratio and 1px for the border of the input field.
    • We break down this into padding: 0 var(--base-ratio) which implicitly sets the padding-left to 4px
    • The other 9px are set using the margin-left property to 2 * var(--base-ratio) + 1px
    • The combination of padding and margin is done to create 4px of white space around the x-axis of the legend.
  • transform the legend element in the y-axis by 100% + 3 * var(--base-ratio) + 1px. Because the input element has padding-top set to 3 * var(--base-ratio), and the 1px because of the top border in the input element.
  • Set the background to transparent color
  • Make sure all changes have transition
legend {
  font-size: var(--typography-size);
  padding: 0 var(--base-ratio);
  margin-left: calc(2 * var(--base-ratio) + 1px);
  transform: translateY(calc(100% + 3 * var(--base-ratio) + 1px));
  background: transparent;
  transition: all 0.75s ease;
}

When the input::placeholder is not being shown, the user is typing, select the legend element and change its color to hotpink, background to white, and transform to 50% in the y-axis, translateY(50%). Adjust these values to your desire.

This is the floating label effect.

Optionally, we can set the padding:0, this sets the padding-left:0 implicitly, and the legend will float to the top left corner of the input field.

input:not(:placeholder-shown) ~ legend  {
  color: hotpink;
  background: white;
  transform: translateY(50%);
  /* optional */
  /* padding: 0; */ 
}

And finally for the label.

  • Set the cursor to what one would normally get on a placeholder, that is text cursor.
  • Set the font-size to the --typography--size variable.
  • As a bonus,
    • Allow font-size transition
    • When the input::placeholder is not shown, select the label and decrease the font-size
label {
  cursor: text;
  font-size: var(--typography-size);
  transition: font-size 0.75s ease;
}

input:not(:placeholder-shown) ~ legend > label  {
 font-size: calc(var(--typography) * 0.9);
}

Hopefully I have shown you that HTML elements can already do a lot, the main part of this implementation lies on translating the legend element from its natural position to align with the input::placeholder, the rest are minor tweaks!

There's some more refactoring that could be done at the fieldset level, for example, calculate 3 * var(--base-ratio) as yet another CSS variable.

Happy hacking!

Posted on Jun 3 by:

icyjoseph profile

Joseph

@icyjoseph

Developer from Peru, living in Sweden :) I am part of Evolve Technology. I do JavaScript, TypeScript, React and Rust!

Discussion

markdown guide