Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: David Bainbridge
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: David Bainbridge ( @redwhitepine )

Rendering Elements After The HEAD Tag In JavaScript

By on

The other day, when I was exploring the progress bar in Hotwire, I noticed that the <div> implementing the progress bar was injected into the DOM (Document Object Model) after the <head> tag and before the <body> tag. I didn't know that it was possible to render elements outside of the Body tag. And, in fact, statically rendered elements outside of the Body will be "moved" into the Body (by the browser) as the HTML is parsed. However, it turns out that you can render elements between the Head and Body tags at runtime using JavaScript.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

I assume that the Hotwire framework is making this choice because it swaps out the entire <body> tag when a new page is loaded. As such, any elements contained within the <body> tag will be implicitly removed. Which means, if the progress bar is outside the Body tag, the progress bar can be persisted even as the Body tag is being replaced.

That's a cool use-case; but, when I saw this, my immediate thoughts went to stacking context. Stacking context is the key to understanding z-index layering. If you've ever seen a developer using a CSS property like:

z-index: 999999999 ;

... it's because they don't understand how z-index works; and, they're just throwing numbers at the wall to see what sticks. It's a futile attempt to break free from a "stacking context" that is locking-down layering within a branch of the DOM.

The main rendered branch of the DOM is the <body> element. Which means, if we can create a "stacking context" on the Body tag itself, anything outside of the Body, will be in a different stacking context and can freely stack above or below the Body layer.

To demonstrate this, I've created a page with two <div> elements using z-index: 2. They are both defined in between the Head and Body tags; however, one of them is dynamically injecting itself after the <head> using JavaScript:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<link rel="stylesheet" type="text/css" href="./demo.css" />
	<style type="text/css">

		/**
		* Creating a STACKING CONTEXT around the body.
		*/
		body {
			position: relative ;
			z-index: 0 ;
		}

		/**
		* Creating a CONTROL object for layering. Using a z-index of "999999", which is
		* HIGHER than the other two elements (below). However, the stacking context on the
		* BODY element changes how things are visually stacked.
		*/
		.box {
			position: fixed ;
			z-index: 999999 ; /* <--------- HIGH z-index value. */
		}

		.static {
			border: 2px solid blue ;
			position: fixed ;
			z-index: 2 ; /* <--------- LOWER (than BOX) z-index value. */
		}

		.dynamic {
			border: 2px solid hotpink ;
			position: fixed ;
			z-index: 2 ; /* <--------- LOWER (than BOX) z-index value. */
		}

	</style>
</head>

<!-- !!!! AFTER HEAD ELEMENT. !!!! -->
<!-- !!!! AFTER HEAD ELEMENT. !!!! -->

<div class="static">
	I am <strong>STATICALLY defined</strong> to be after the Head element.
</div>

<div class="dynamic">
	I am <strong>DYNAMICALLY defined</strong> to be after the Head element.

	<script type="text/javascript">
		document.head.after( document.querySelector( ".dynamic" ) );
		// Remove the Script tag from the DOM (for funzies!).
		document.currentScript.remove();
	</script>
</div>

<!-- !!!! BEFORE BODY ELEMENT. !!!! -->
<!-- !!!! BEFORE BODY ELEMENT. !!!! -->

<body>

	<h1>
		Inserting Elements After The HEAD Tag In JavaScript
	</h1>

	<div class="box">
		<br />
	</div>

</body>
</html>

As you can see, the "box" element is our control layer and has a high z-index. Each of my other elements use a z-index of 2. Normally, this would place both elements below the box from a stacking perspective. However, when we run this page, we get the following output:

The dynamically injected element is stacked above the box while the static element is stacked below the box.

As you can see, the browser moved the statically defined element into the Body tag as it parsed the HTML, which is why it is layered below the "box". However, the dynamically defined element (which uses JavaScript to inject itself after the Head tag), is allowed to remain after the <head>. And, because it is rendered outside of the <body> tag, it is stacked above the "box" despite having a much lower z-index.

Accessibility Concerns

I don't know that much about accessibility. But, I have to assume that content rendered outside of the <body> has all kinds of accessibility issues. As such, it's probably a bad idea to use this technique to render consumable "content". However, for something like a progress bar that is nothing more than a visual indicator (ala the Hotwire framework), this seems like a rather clever approach.

Want to use code from this post? Check out the license.

Reader Comments

15,663 Comments

I also wanted to try playing around with this technique to create a persisted IFrame-based video play in Hotwire:

www.bennadel.com/blog/4413-persisting-an-iframe-based-video-player-across-page-visits-with-hotwire-and-lucee-cfml.htm

Essentially, I inject the Hotwire Turbo Frame before the <body> tag. This way, I can mount an IFrame inside of it, and Turbo Drive won't have to move it around, and won't control the state as I move from page to page.

1 Comments

I didn't know that elements can be rendered outside of the body tag, that's interesting. Thank you for your walkthrough of this topic, as well as the examples you provide.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel