DEV Community

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

Posted on

Use more HTML Elements - Floating Form Field Label

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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; */ 
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)