Mathias Bynens

Displaying hidden elements like <head> using CSS

Published · tagged with CSS, HTML, JavaScript

By default, only the html and the body element (plus its descendants) of a web page are actually rendered. All information within the head element might be parsed and used by the browser, but most of the time it doesn’t get displayed. If you want to, you can use CSS to display these ‘hidden’ elements.

I used to do this on my old site, but then I renewed it, so I just thought I’d document this little ‘trick’.

Wait, what?

Look at it this way… Browsers apply the following CSS rule to every document:

head, title, link, meta, style, script {
display: none;
}

As with all browser-default CSS, this can be overridden by declaring the following style rules:

head, title, link[href][rel], meta, style, script {
display: block;
}

This one CSS rule is all it takes to force the browser to display the elements.

Of course, there are a few other things you might want to do — adding inner content to the link and meta elements, for example.

Use generated content for added coolness

link[href][rel]::after {
content: attr(rel);
text-transform: capitalize;
}

meta[charset]::after {
content: 'Charset: ' attr(charset);
}

meta[name][content]::after {
content: attr(name) ': ' attr(content);
text-transform: capitalize;
}

Similarly, we can style script elements with a src attribute specified:

script[src]::after {
content: 'External file: ' attr(src);
}

To target only inline script blocks, we could use the CSS3 negation pseudo-class:

script:not([src]) {
background: lime;
}

Use JavaScript for even more cross-browser coolness

Firefox is the only browser that seems to automatically create clickable links out of visible link elements with a href attribute. This is pretty useful — the link element sort of becomes a hyperlink pointing to the resource it was referring to. To clone this behavior in other browsers as well, we can use JavaScript:

<script>
function() {
var head = document.head || document.getElementsByTagName('head')[0];
var links = head.getElementsByTagName('link');
var length = links.length;
while (length--) {
links[length].onclick = function() {
location.href = this.href;
};
}
}());
</script>

This script simply loops through all link elements inside <head> and adds onclick handlers to them, causing the referenced document to be opened upon clicking.

Demo

You can see this technique in action on the demo page. Believe it or not, this is a document with an empty <body>!

Sadly, this fails in Internet Explorer (I’ve tested versions 6, 7, 8, 9 and 10). Nonetheless, I think this is a pretty interesting trick.

About me

Hi there! I’m Mathias. I work on Chrome DevTools and the V8 JavaScript engine at Google. HTML, CSS, JavaScript, Unicode, performance, and security get me excited. Follow me on Twitter, Mastodon, and GitHub.

Comments

wrote on :

riddle: Thanks for the tip! So, according to the spec, is script:not(script[src]) supposed to work or not? It seems to work in nightly WebKit builds.

riddle wrote on :

Yes, that’s right. See, script[src] isn’t simple – it’s like .class1.class2 (note the lack of space), one’s refinement by the other.

And when you really think about it, you basically say: take all script elements and dismiss all script elements that have a src attribute. It’s clearly redundant – we only have a bunch of script elements anyway. Using an attribute just makes sense. :)

But yeah. Just simple. Meaning .class, [type="text"], #id or div. :)

Diego Perini wrote on :

By default, only the body of a web page is actually rendered.

The html element is also rendered and you can change it by means of CSS rules like any other element (and this is somewhat useful).

I still don’t get how changing the display property of meta, script and style elements could be thought as useful for the WEB :) There are a lot of things that can be done in an HTML document, the question is: are these things useful?

And about script:not(script[src]), it is incorrect by specs and doesn’t work in my Safari 5. Further on, even if that was working, you are slowing down the browser selector engine by repeating twice the script tag when only one is enough if written as the specs recommend. Frameworks like jQuery will accept the wrong syntax you are suggesting but that’s an error as far as specifications are considered.

In this case jQuery will return inconsistent results between browsers having querySelectorAll() and those not having it (IE, Firefox < 3.1, Opera < 10 etc.).

I prefer cross-browser consistency over crippled extensions to the specs.

That said, specifications change frequently and I could have to retract some of those assertions one day, though I believe this to be highly improbable.

wrote on :

Diego: You’re right about the html element being rendered as well. I should have worded that more carefully. This is indeed very useful, because the html element can be used as a body wrapper in CSS. I’m actually doing that on this site (and every other site I develop).

I never said making hidden elements visible through CSS is a ‘useful’ technique, nor that people should actively start using it in production; I just found it to be interesting and good to know.

Johan Sundström wrote on :

It would be neat with a little bookmarklet that crafts a stylesheet for the current page which simply adds a <style> element (or, if present already with the exact CSS added, toggles its boolean .disabled property) that adds the appropriate "display: block":s, the "*:before":s and "*:after":s matching all the exact html tags and attributes used in the page (making html, head, body, meta:s, script:s, link:s, style:s, and all the other non-rendered tags show, with their values, as they would look in the page source (probably whitespace: pre-wrapped), greatly reducing the need for “view source” for mere inspection (or even some copy-and-paste type operations). Example:

script:not([src]):before {
content: '<script>'
}
script[src]:before {
content: '<script src="' attr(src) '">'
}
script:after {
content: '</script>';
}

I guess the js part has to permute all the {has-/lacks-}attribute combinations for the :before:s itself, which might get hairy for multi-attribute elements found, but it would be a rather useful (and cool :-) tool.

Neat feature with the contenteditable attribute addition for script and style tags, by the way!

Burak Yiğit Kaya wrote on :

Great trick! I tried it on IE9 and IE10 though and it does not work.

Leave a comment

Comment on “Displaying hidden elements like <head> using CSS”

Your input will be parsed as Markdown.