1 Thing A Week 1 Thing
A Week

Android style-form labels with pure CSS

Week 234.0 — 18th April '22

I've seen the way Android presents form labels replicated in many ways, mostly involving JavaScript. I've tried my hand at recreating with pure CSS but it means butchering the HTML so that the labels are after the inputs and styled correctly. By placing the label after the inputs, you can use the following CSS selector to target them while using flexbox to re-order the properties on the page. Fine for users, sounds terrible for accessibility.

input + label {
    // default label CSS 

input:focus + label {
    // label CSS for when the input has focus.

Thanks to :has(), we no longer need to rely on JavaScript or sketchy HTML markup. CSS can finally handle the presentation for this use case like it's supposed to.

If you think of the :has() selector as a parent selector, then you can apply the styles to it's children if those children exist. Click into the fields in the demo below to see the labels become more prominent.

The above demo will only work in Safari at the time of writing.

The CSS isn't really as clean as it was in the example code above, but I'd much rather a bit more CSS than having to deal with JavaScript.

li label {
    box-sizing: border-box; // helps with positioning
    display: block; // label will appear above the input, instead of before it
    height: calc(var(--base) * 2.5); // helps make the animation smoother
    transition: font-size var(--timing),
                padding var(--timing),
                color var(--timing);
li:has(label + input) label { // default, unfocused styles
    color: var(--unfocused);
    font-size: calc(var(--base) * 1.5);
    padding: calc(var(--base) / 2) 0 0;
li:has(label + input:focus) label { // focused styles
    color: var(--focused);
    font-size: calc(var(--base) * 1.75);
    padding: calc(var(--base) / 4) 0 0;

I don't know if it's a Safari bug, which is the only browser you can test in right now, but I couldn't get the labels to use the focused style when not empty. It might be that my selectors were not right but I tried separately and got nothing.

Over all though, :has() cannot come to all browers soon enough. I've been playing with a pure CSS collapsible menu and will share details on it soon.

More from 1 Thing A Week
« Money raised for MIND by the team at magLabs Some upcoming changes to the content on 1 Thing A Week Why is buying a TV such a horrible experience? Some use cases for the :has() selector in CSS »



Quick links and commentary on interesting articles, videos and more throughout the week.

  1. 234.4 – Thursday
    1. EU moves one step closer to mandating Apple to switch iPhone, iPad, and AirPods to USB-C macrumors.com
  2. 234.5 – Friday
    1. The mass delusion of the pandemic being over kottke.org
    2. Contra Chrome kottke.org

Jump to notable items for Week #233, Week #235 , view the notable archive or my favourites.

About 1 Thing A Week

A website curated by @cchana bringing you 1 Thing A Week, delivered every Monday morning.

You can find a list of all articles in the archive, or read the most popular.

Find out more about 1 Thing A Week or read the privacy policy.


Visit the market and support 1 Thing A Week.

You can find 1 Thing A Week on Twitter, Facebook, Instagram & Reddit