Got here looking for a way to implement floating labels as well, and figured an updated 2024 answer might help others as well.
The :has selector being supported pretty much anywhere (https://caniuse.com/css-has) opens up new possibilities.
For this method to work, all the fields will need to have placeholders. This is so that we can use :placeholder-shown
to detect whether an input has been filled or not. For that, we can use a very slightly adapted snippet from BusinessBloomer (https://www.businessbloomer.com/woocommerce-move-labels-inside-checkout-fields/)
All we need to do is remove the second line in the foreach statement, to avoid setting the labels to an empty string. So the final snippet would be:
add_filter( 'woocommerce_checkout_fields', 'bbloomer_labels_inside_checkout_fields', 9999 );
function bbloomer_labels_inside_checkout_fields( $fields ) {
foreach ( $fields as $section => $section_fields ) {
foreach ( $section_fields as $section_field => $section_field_settings ) {
$fields[$section][$section_field]['placeholder'] = $fields[$section][$section_field]['label'];
}
}
return $fields;
}
With that snippet, all our fields should have placeholders, equal to the content of their label.
Now for the CSS:
First we set the .form-row
to position relative, so that our labels don't end up flying around on the page:
.form-row {
position: relative;
margin-bottom: 25px;
}
You might also wanna give them a margin-bottom, at least on mobile so the floating labels don't go into the field above.
/* Hide our placeholders by setting its opacity to 0 */
.form-row input::placeholder {
opacity: 0;
}
/* Absolutely position our labels, give them some color and a transition */
.form-row label {
position: absolute;
top: 50%;
left: 15px;
transform: translateY(-50%);
transition: .25s all ease-out;
color: #333;
font-size: 16px;
}
/* Apply styles when the input is in focus or the placeholder isn't shown aka. the input has been filled */
.form-row:has(input:focus) label,
.form-row:has(input:not(:placeholder-shown)) label {
top: 8px;
left: 0;
transform: translateY(-155%);
color: firebrick;
font-size: 13px;
}
Keep in mind that the positioning values will also depend on whatever other styles you have applied to your form, you'll likely have to adapt a bit but in the end it should look something like this:
In my case, the shop only sells to a single country so the Country/Region Label should be unaffected by all that. In order to exclude it, I just had to slightly adapt my selectors using :not
to:
.form-row label:not([for="billing_country"]) {
...
}
.form-row:has(input:focus) label:not([for="billing_country"]),
.form-row:has(input:not(:placeholder-shown)) label:not([for="billing_country"]) {
...
}
Hope it helps someone :)