html vs htl.html

I’ve gotten along fine using html` ` for HTML tagged template literals in Observable, but I am aware that htl.html` ` is now the recommended approach.

Is there a document that articulates the differences between the two? Why is htl better? Is it a drop-in replacement, or are there cases where switching from html to htl.html will break things? Are there specific use cases that are easier with htl?

I’m aware of the html documentation here:

And the htl documentation here:

But, the differences don’t jump out. In both cases you put in HTML, interpolate variables with ${}, and you get out DOM.

Your insights would be greatly appreciated!


1 Like

It’s not a huge difference in my experience, but htl.html is somewhat safer, stricter, and (most helpfully in my experience) lets you attach event listener functions inline. Here’s a notebook with side-by-side examples (same code, except swapping html out for htl.html):


The main difference is that Hypertext Literal is safe by default in re. to escaping. (See the Why not concatenate? discussion in the README.) Because this safety is not backwards-compatible folks need to opt-in to Hypertext Literal, but eventually Hypertext Literal will become the default on Observable.

The other big difference is that Hypertext Literal provides a variety of conveniences: function attributes (typically for event listeners), boolean attributes (such as disabled), nullish attributes (where you don’t want the attribute to be generated if the value is nullish), spread attributes (for setting multiple attributes from an object), style objects (for setting styles from an object), iterables, etc.


Thanks for your replies @tophtucker and @mbostock.

I find the side-by-side comparisons SUPER helpful.

I’ll note another difference I’ve stumbled on:

  <div>My text</div>


<div>My text.</div>


  <div>My text.</div>


  <div>My htl.html div</div>

I’m not sure what the rationale for this distinction is, but it has caught me out a couple times when experimenting around with switching from html to htl.html.


Oooh great example, I remember running into that when I was switching! I remember that broke a couple things for me. Added that comparison to the notebook.

I think it’s again more strict — the whitespace surrounding the <div> is a valid text node or whatever it’s called, so the code technically contain three child nodes (text, the div, more text). If your code only contains a single child, then only that child is returned; otherwise, it encloses all children in a span. I’m just skimming and Mike might correct me, but I think you can see where that happens in the HTL code here: htl/index.js at main · observablehq/htl · GitHub

1 Like

Good read, Toph! Yes, the old HTML tagged template literal would implicitly trim surrounding whitespace, which made it hard to intentionally introduce whitespace. The new Hypertext Literal does not trim whitespace, which means that it needs to surround the content with an implicit SPAN element. Alternatively, you can use htl.html.fragment to get a DocumentFragment, which is useful if you’re going to interpolate that into another literal.

Also, be aware that Observable’s Inspector will refuse to append a DocumentFragment to the DOM (that is, display its contents). To quote from the Inspector source:

This deliberately excludes DocumentFragment since appending a fragment “dissolves” (mutates) the fragment, and we wish for the inspector to not have side-effects.