Can we do this without getting too hacky?

In case you weren’t aware, an <input /> doesn’t allow the usage of ::before or ::after pseudo elements on it. None of the 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. So, 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.
(Actually, I used :before because we had to support some older browsers, but I digress.)

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.


In talking this problem out with @artlawry, we came up with the following solution:

First the HTML

  <label for="btn_cta" class="btn-icon">
    <span class="hide">Submit the form</span>
    <input type="submit"
           value="Call to Action" />

And some CSS to get the basic idea

  .btn-icon {
    position: relative;

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

  .hide {
    color: transparent;
    font-size: 1px;
    left: -99999px;
    position: absolute;

Pretty simple. But why a label?

I could have wrapped the __input__in a <span> or a <div>, but by positioning a pseudo element from either of those wrappers on top of my ‘button’ would have resulted in an unclickable area. And that’s obviously not optimal.

By instead wrapping the input in a label and using the for=" " attribute to target the input’s ID, the label becomes a hit area for the input, and thus the pseudo element can be clicked on and still fire the input’s action.

Here’s an example on codepen:

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

This is a hack

This method got me out of a pinch but it is a hack and I really don’t recommend it unless you are willing to do something like this in the short term, only to go back and fix it later.

Why is this a hack? Well, it’s a misuse of the label element for starters. Adding in the <span class="hide"> was my way around feeling completely deplorable for what otherwise would have been an empty label around an input element. Labels have significant semantic, accessible meanings and like all HTML elements, we should always strive to use the correct element for the task at hand.

In the end

The proper solution to my initial problem was to convert all inputs that needed this icon to <button type='submit'>Text</button>. So, while I wanted to share this trick with you, my next order of business is to undo the wrongs I’ve committed.