Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Lisa Tierney
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Lisa Tierney

HTML Templates Can Be Mutated Just Like Any Other DOM

By on

I've always thought of the HTML <template> element as being static. And, if I needed to modify a template's contents, the modification would be performed after the template content was cloned (and before it was injected into the DOM). As such, I've never thought too deeply about the template mechanics themselves. I was, therefore, delightfully surprised this morning when I went to modify a template's content and it just worked—just like any other DOM (Document Object Model) mutation.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

To demonstrate, I'm going to use Alpine.js to dynamically insert a <template> element into the DOM with the click of a button. But, I'm also going to use setInterval() - unrelated to Alpine.js - to alter the contents of said <template> element:

<!doctype html>
<html lang="en">
<body>

	<h1>
		HTML Templates Can Be Mutated Just Like Any Other DOM
	</h1>

	<div x-data="{ isShowing: false }">

		<button @click="( isShowing = ! isShowing )">
			Toggle Template
		</button>

		<template x-if="isShowing" class="counter-template">
			<p>
				Counter: <strong class="counter">0</strong>
			</p>
		</template>

	</div>

	<script type="text/javascript" src="../../vendor/alpine/3.13.5/alpine.3.13.5.js" defer></script>
	<script type="text/javascript">

		var counter = 0;

		// Update the contents of the template every second.
		setInterval(
			() => {

				var target = document.querySelector( ".counter-template" )
					// The template content represents a detached document fragment that
					// provides its own DOM that can be inspected using querySelector().
					.content
					.querySelector( ".counter" )
				;

				target.innerText = ++counter;

			},
			1000
		);

	</script>

</body>
</html>

As you can see in my JavaScript code, I'm calling the .querySelector() method twice. The contents of the <template> element aren't quite in the DOM (depending on how hard you squint)—they're represented in their own Document Fragment. As such, in order to access the <strong> tag that resides within the <template>, we must first pass through the .content property. And, once we're inside the fragment, we can mutate it just like we would any other document object model.

Now, when we run this Alpine.js demo, we can see the template contents updating every second:

Browser elements tab showing that the counter value, within the template, is being updated every 1,000 milliseconds.

As you can see in the browser's Elements tab, the innerText of the <strong> tag is being updated every second by our setInterval() operator. Then, when we use Alpine.js to toggle the template into the DOM, the state of the template at that moment in time is what gets cloned into the web page.

I don't know how I'll use this exactly. But, knowing that a <template> element's contents can be mutated prior to cloning is definitely something that'll be helpful in an edge-case somewhere.

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

Reader Comments

205 Comments

"depending on how hard you squint" made me laugh 🤣

Weird (and interesting) you have to go through the .content property though. Wonder why 🤔

15,674 Comments

@Chris,

Yeah, this is where I just don't have the good words to explain it properly; which is partly because I don't understand it 100%. The .content property (as described by MDN) says:

A read-only DocumentFragment which contains the DOM subtree representing the <template> element's template contents.

So, this term, "subtree", makes me think this is most technically part of the DOM - just a "non rendered" subtree 🤔 It's not nuance that I often think about, so I just don't have the good explanation at my fingertips.

205 Comments

@Ben Nadel,

Thanks for that reference (and the link), which I've checked out. But now that just leaves me with another question. It specifically says it's "read-only" and yet, you were able to mutate within. Maybe because it's a sub-element within the contents? IDK, but from the documentation alone...I would not have expected to be able to mutate the template contents.

I guess that's why we (meaning you) experiment, right?!

15,674 Comments

@Chris,

Great question. I assume all that means is that you can't directly overwrite the .content property itself. So, you couldn't, for example, progammatically call document.createFragment() and then try to assign it to a template element. But, I'm just shooting from the hip here 😆

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