In case you weren’t aware, an <input> doesn’t allow ::before or ::after pseudo elements. None of the different input types do.

Why can’t you use these pseudo elements on inputs? Because these pseudo elements are only allowed to be used on container elements. Elements like inputs, images, and any other self closing element can’t use pseudo elements because they aren’t “container elements”. Meaning, they don’t allow any nested elements or content inside of them.

I was going somewhere with this…

Anyway, I was recently tasked with adding an icon to various call to action links / buttons on a website. The easiest way to do this was by replacing previous class name (which was still needed on some elements) for the new one. The new class using a ::before {} to position the icon on top of the existing button, which already had a set background.

However, a problem arose when some of the buttons that needed to be updated were <input type="submit"> elements. These elements couldn’t be changed to <button>s, which would have allowed the pseudo element, due to styling overwrites / rewrites I would have had to make to buttons in general, and refactoring the code wasn’t an option at this time.

Boo…

Hacking around with this some more, while I couldn’t modify the input element, I could wrap the input in other elements. This lead me to the following…

First the code

<span for="btn_cta" class="btn-icon">
  <input type="submit"
   id="btn_cta"
   class="updated-btn-class"
   value="Submit" />
</span>

And some CSS to get the basic idea:

.btn-icon {
  display: inline-block;
  position: relative;
}

.btn-icon::before {
  background: url(/icon.png) no-repeat center center;
  background-size: 100% auto;
  height: 100%;
  left: 0;
  pointer-events: none; /* important */
  position: absolute;
  top: 0;
  width: 2em;
}

Quick breakdown

By wrapping the input in a span the submit button “acquires” the ability to have pseudo-elements.

Here’s an example on CodePen:

See the Pen Using a label to give an input a pseudo element by Scott (@scottohara) on CodePen.

Adding the pointer-events: none to the ::before pseudo-element makes sure that the icon doesn’t block mouse clicks or taps from reaching the input.

This is a silly hack

This method got me out of a pinch, but it is a hack and I really don’t recommend it unless you need to do quickly do something like this, and can go back and fix it later.

Why is this a hack? Because we have the <button> element which can have pseudo-elements. I suppose this could be helpful for other text inputs that need an icon to be placed “within” the text field. But in many instances those icons are likely stand-ins for labels or other functional controls (e.g. show password or a search button). Those are “less hacky” use cases as they potentially serve functional purposes rather than being decorative only.

In the end

The proper solution to my initial problem was to convert all inputs that needed this icon to <button type="submit">s. So, while I wanted to share this trick, my next order of business is to figure out a way to hack the source code and change the input to a button.