Is there a reason why this CSS doesn't work?

a[href^="http"]:after {

a[href^="http"] img ~ :after {

.. on this HTML?

<a href="">Test</a>
<a href="">
    <img src="">

The idea is to have a pseudo-element on matching anchor tags. But I do not want it to apply to anchor tags that wrap an image. And since I can't target anchors using something like a < img, I figured the maybe I could target the :after pseudo-element by finding an image that it's a sibling of.

Any insight would be greatly appreciated.

Solution 1

You can't target :after since it's content is not rendered in the DOM and it does not manipulate it - for this to work the DOM would have to be re-rendered and CSS can't manipulate it like this.

Check the specification for detailed understanding:

Generated content does not alter the document tree. In particular, it is not fed back to the document language processor (e.g., for reparsing).

I suggest you use JavaScript to do the job for you.

Solution 2

You cannot use a combinator to target a pseudo-element relative to elements other than its generating element.

This is because they're pseudo-elements, not actual elements, and combinators only work by establishing relationships between actual elements. A pseudo-element, on the other hand, can only be applied to the subject of a selector (the rightmost compound selector), and this happens only after matching is processed on the real elements. In other words, matching is done first as though the pseudo-element wasn't there, then the pseudo-element, if it's indicated within the selector, is applied to each match.

In your code, the following selector:

a[href^="http"] img ~ :after

Does not actually look for an :after pseudo-element that comes after an img within the a, even though it appears that way as both are rendered as children of the a element.

It can be rewritten into the following:

a[href^="http"] img ~ *:after

Notice the * selector, which is implied. Similarly to how you can omit * before any other simple selectors for it to be implied, omitting * from a pseudo-element also makes it implied to be there. See the spec for details.

Now, even though it appears *:after should still match a:after (since a would match *), it still doesn't work that way. If you remove the :after pseudo-element from the selector:

a[href^="http"] img ~ *

You'll notice that the meaning of the selector changes entirely:

Select any element
that appears as a following sibling of an img
that is a descendant of an a (whose href starts with "http").

Since the img is the last child of the a element in your HTML, there are no following siblings to match, and therefore no :after pseudo-elements can be generated.

In the case of a :before or :after pseudo-element, one might think of matching the pseudo-element's generating element relative to the pseudo-element's "sibling", but as the OP has correctly pointed out, there is no parent selector, so they're out of luck there, too.