Reach New Heights.

Front-end web development to step up your game.

Monthly Archives: July 2016

Styling forms with pseudo elements: can it be done?

I was recently building a site that required a form with a very minimalist design.  While styling the input fields, I discovered some interesting behaviour that I thought I should share.  The design called for text input elements with simple underlines rather than complete borders or backgrounds that stand out from the rest of the form.  Additionally, the design specified that an underline should increase in height when the text input element is in hover state.

To illustrate what I encountered, let’s walk through the process of executing the design.  We’ll start with a heading and a basic form with default styling:

Simple form – HTML
Simple form – Result

Now, in order to differentiate the form from the rest of the page and work towards the design, we’ll add some styles in our CSS:

Boilerplate Styling
Basic form styling – CSS


Form with basic styling
Basic form styling – Result

With our form taking shape, we’ll now try to bring it closer to the simple underlined style of the design.  To do this we need to add to our input[type=text] rules to override the default borders first and then apply a bottom border:

This is looking good so the last order of business is the hover state.  If we try to increase the value of border-bottom for input[type=text]:hover, we’ll notice that on hover, the input element grows taller by the difference between the original border width and the thicker one, causing the elements that follow to move. It’s pretty distracting and looks sloppy so this isn’t an option.

So how can we achieve the border effect that we want without disrupting the layout?  A common method for adding a decorative line to an element is to attach a pseudo element.  The pseudo element is absolutely positioned, so its footprint in the layout flow is removed.  Therefore, it does not disrupt the layout flow.  Let’s try this technique.  To position the pseudo element relative to the input element, we’ll first remove the transition from input[type=text] and the input[type=text]:hover rule that we added in the previous step. Next, we’ll set input[type=text] to position:relative. Then we’ll create the pseudo element by setting styles for input[type=text]:after and the hover state with input[type=text]:hover:after.

Input element with pseudo element – CSS

But now, nothing happens when we hover over a text input element.  What’s going on?  The problem is that input elements cannot have pseudo elements :before and :after
attached to them, because they are empty elements and may not have any child nodes, like nested elements or text.  The same goes for other elements which do not have closing tags, like <img>, <hr> and <br>.  So what can we do?

We need container elements to attach our pseudo element borders, so we’ll wrap each text input element in a <div> with its position set to relative. As input element will occupy the full width of the <div>, it will appear as though the pseudo element is attached to the input.  Our form markup now looks like this:

Nested input elements – HTML

Next, we’ll modify the CSS rules to make the <div> elements the parents of the pseudo elements and trigger hover effects, rather than the text input elements.

Container element with pseudo element – CSS

The effect works well now but the markup has become somewhat bloated as a result of the additional wrapper divs.  This presents us with a dilemma, since it’s considered best practice to avoid the overuse of the <div>, yet nesting an empty element in a container appears to be the only means of achieving the desired effect in this case.

The cleanest alternative would be to simply change the color of the border as a hover effect, without changing the width:


This is the solution I ended up implementing.  Of course this is a compromise, as it does not completely achieve the desired effect.  To conclude, I’ll address the question posed in the title of this article.  While it is certainly possible to style form inputs with pseudo selectors by way of the workaround I noted above, it is probably not a good idea to do so in the interest of semantic structure.