I read this article, which motivated to build the floating form field using two seemingly forgotten HTML elements:
Expectation
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 theinput::placeholder
. - When the
input::placeholder
is not shown, translate thelegend
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 to14px
. - Declare a
CSS
var named--base-ratio
, set to4px
. - Set
font-family
tomonospace
. - Set
padding
to0
- Set
border
tonone
.
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 elementfont-family
by default, so do so. - Declare a
border
of1px
withsolid
style line, and color#c3c3c3
. - Set
margin
to0
. - Set
padding
to0
. - 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
to3
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 theinput::placeholder
text we need to account for13px
or rather3
times the--base-ratio
and1px
for the border of the input field.- We break down this into
padding: 0 var(--base-ratio)
which implicitly sets thepadding-left
to4px
- The other
9px
are set using themargin-left
property to2 * 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.
- We break down this into
-
transform
thelegend
element in the y-axis by100% + 3 * var(--base-ratio) + 1px
. Because theinput
element haspadding-top
set to3 * var(--base-ratio)
, and the1px
because of the top border in theinput
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 istext
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 thelabel
and decrease thefont-size
- Allow
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, calculate3 * var(--base-ratio)
as yet anotherCSS
variable.
Happy hacking!
Top comments (0)