How to add a margin after an element only if followed by text

Viewed 1977

I have buttons on my page, sometimes with icon images and a text label in them, sometimes only with icon, no text.

I've styled the images in the buttons so that there is a margin to the right, to separate the icon from the text.

The problem is that because of that margin, if there is no text in the button, the icon is not in the center of the button, so it looks crappy.

enter image description here

button {
  border: none;
  border-radius: 0.5rem;
  background-color: pink;
  line-height: 32px;
  font-size: 1rem;
  padding: 0.5rem;
}

button > img {
  float: left;
  margin-right: 0.5rem;
}
<div>
  <p>
    This looks OK:
  </p>
  <button>
    <img src="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/star-32.png"> OK
  </button>
</div>
<div>
  <p>
    This doesn't (icon not in the center of the button):
  </p>
  <button>
    <img src="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/star-32.png">
  </button>
</div>

(see fiddle)

An easy and obvious fix would be to put the text in a span element and then style the image element with :only-child.

But then I would have to change a lot of places in our code base that already use this style and I would have to somehow convince my fellow developers to always put their button labels in span elements, only to make the buttons without text labels look OK.

Is there another way that allows me to keep my HTML code the same while getting rid of the margin on the image if it is not followed by text?

I have tried experimenting with :not and :empty pseudo class selectors but couldn't make it work. The problem is that you cannot select or style plain text nodes.

2 Answers

I'd go with

button { display: flex; text-indent: 0.5rem; }

You can then get rid of the styling for the img tag altogther

To expand a bit on @rejas answer - it can be done if all the spacing is the same size.

EDIT: Since Chrome 87, I think, there is a bug that prevents the below solution from working - https://bugs.chromium.org/p/chromium/issues/detail?id=1159311. Fortunately there is a workaround, but only for newer browsers, that support CSS Grid. So, the new combined solution should be something like this:

button {
    border: none;
    border-radius: .5rem;
    background-color: pink;
    line-height: 32px;
    font-size: 1rem;
    padding: 5px 10px 5px 0;
    vertical-align: middle;
    display: inline-flex;
    text-indent: 10px;
}

button::-moz-focus-inner {
    border: 0;
}

button > img {
    margin-left: 10px;
}

@supports (display: grid) {
    button {
        text-indent: 0;
        padding: 5px 10px;
        display: inline-grid;
        grid-gap: 10px;
        grid-auto-flow: column;
    }

    button > img {
        margin-left: 0;
    }
}
<button>
    <img src="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/star-32.png">OK
</button>

<button>
    <img src="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/star-32.png">
</button>

<button>
    OK
</button>

Of course, you can choose to support just newer browsers, in which case you can have your grid-gap different from the button padding, allowing for greater flexibility.

The old solution:

button {
    border: none;
    border-radius: .5rem;
    background-color: pink;
    line-height: 32px;
    font-size: 1rem;
    padding: 5px 10px 5px 0;
    vertical-align: middle;
    display: inline-flex;
    text-indent: 10px;
}

button::-moz-focus-inner {
    border: 0;
}

button > img {
    margin-left: 10px
}
<button>
    <img src="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/star-32.png">OK
</button>

<button>
    <img src="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/star-32.png">
</button>

<button>
    OK
</button>

Related