Templating in HTML

(kittygiraudel.com)

200 points | by clairity 583 days ago

21 comments

  • spankalee 582 days ago
    <template> is great, but some people are surprised that it doesn't include any form of parameterization or expressions. The entire process of cloning a template and updating it with data is left up to the developer - it's pretty low level.

    That's why my team made the lit-html library, which uses JS tagged template literals to make <template> elements for you, clone them and interpolate data where the JS expressions are, and then update the result really, really fast with new data:

          import {render, html} from 'https://unpkg.com/lit?module';
    
          let count = 0;
          
          function increment() {
            count++;
            renderCount();
          }   
          
          function renderCount() {
            render(html`
              <p>The count is ${count}</p>
              <button @click=${increment}>Increment</button>
            `, document.body);
          }      
          renderCount();
    
    You can try that out here: https://lit.dev/playground/#gist=1eff9baed1251fc60dd7da8b7f9...

    We're also working with Apple on a proposal called Template Instantiation which brings interpolations and updates into HTML itself (though the pandemic and things really stalled that work for the moment).

    • voxal 582 days ago
      lit-html is great. I use it for a lot of projects whenever I need some simple tempted HTML. That being said, it seems like the v1 documentation for lit-html [1] has been removed in favor of a single page in the v2 documentation [2]. Is using lit-html standalone not recommended anymore? I really liked using it standalone.

      [1] https://lit.dev/docs/v1/lit-html/introduction/ [2] https://lit.dev/docs/libraries/standalone-templates/

      • spankalee 582 days ago
        Standalone use is great and still works fine.

        The organization of the lit-html and lit-element packages didn't change: they're separate and lit-element depends on lit-html. The only difference is that we added the lit package that rolls them both up and we talk about templating in one place in the docs rather than two (lit-html and lit-element used to have separate sites). We did this because most people used lit-element and the separation was confusing to some and a little bit of rough DX for most.

    • wildrhythms 582 days ago
      I use lit-html for front end projects at work and it's a godsend, truly. Being able to keep one central 'state' object and just re-render a template based on that without having to pull in a whole framework, without having to set up a whole webpack build process, is incredible!
    • bugmen0t 582 days ago
      Do you have a link to the template instantiation proposal?
  • verisimilidude 583 days ago
    The <template> tag really shines when used alongside <slot> and the Shadow DOM.

    But I really wish there were an HTML-native way to load <template> from separate files, the same way we do with CSS and JS. Not a show-stopper, but it’d be very nice to have.

    • goatlover 583 days ago
      > But I really wish there were an HTML-native way to load <template> from separate files

      Seems like the JS folks blocked this from happening. It's weird to me to think we've arrived at the point where JS considerations have come to dominate for something that was built to be a document sharing platform.

      • spankalee 582 days ago
        No one blocked it. If you're referring to HTML Imports, the problem with them is that they didn't integrate with JS modules. There's a new HTML modules proposal [1], and with JS import assertions and CSS and JSON modules landing [2], HTML modules are probably not far behind.

        [1]: https://github.com/WICG/webcomponents/blob/gh-pages/proposal... [2]: https://web.dev/css-module-scripts/

        • dmitriid 582 days ago
          goatlover: Seems like the JS folks blocked this from happening.

          spankaalee: not true, they were removed because.... they were't compatible with JS

          ---

          goatlover: JS considerations have come to dominate for something that was built to be a document sharing platform.

          spankalee: here are two proposals which are... Javascript-only, don't work without javascript, and add more Javascript to the platform

          ---

          ¯\_(ツ)_/¯

      • dmitriid 582 days ago
        > where JS considerations have come to dominate

        Not JS. Chrome, and their decade-old crusade to make web components happen, no matter the cost or common sense

    • cantSpellSober 583 days ago
      Yes, I thought that's what this article would be about. Respectfully, the MDN reference for <template> has more information than this post.
  • brundolf 582 days ago
    Sorry, but the <template> tag doesn't actually do "templating in HTML"

    ...but it should. It's shocking to me that we've had the modern paradigm of client-side-rendering frameworks for over a decade now, without so much as an RFC for any kind of native support.

    For the sake of render performance, bundle size, removing a need for transpilation, compatibility across frameworks. There are a million reasons this should be happening in the browser at this point. I'm sure it's a complicated standard to come up with (for one: HTML-based vs JS-based rendering), but why does it feel like nobody's even trying?

    • tannhaeuser 582 days ago
      There's no lack of syntactical templating mechanisms when HTML is hosted within a SGML markup processing context (ie how HTML started life), and no need to add templating at the HTML markup vocabulary level either:

      For server-side rendering, SGML (and also some other template "engines") provide HTML-aware, type-checked, parametric macro expansion. Actually, SGML templating works transparently on the client side and the server side.

      Within the browser OTOH, there's already JavaScript, making every dynamic feature added to HTML inessential for better or worse, like it has for over 25 years now. That's just how it was decided a long time ago, and adding half-assed features to HTML like the template element (which requires JavaScript, in turn, thus doesn't add any essential capability) all the time is exactly the thing we shouldn't be doing when the damn "web stack" is already bloated as fuck.

    • encryptluks2 582 days ago
      Because JS is where the privacy holes are at and keep getting added.
  • labrador 583 days ago
    > Let’s start with the fact that <template> do not enabling you to do anything that’s not possible otherwise. In that way, it’s more of a convenience tool really. If you have significant HTML structures that need to be injected at runtime, it might be very cumbersome to do so manually with document.createElement and element.setAttribute.

    I didn't expect the paragraph to end that way. I would write it:

    Let’s start with the fact that <template> do not enabling you to do anything that’s not possible otherwise. In that way, it’s more of a convenience tool really. If you have significant HTML structures that need to be injected at runtime, you can use "display:none" to hide the HTML structure, then copy node to make and show a copy, for example a dialog box

    Anyway, that's how I've been doing it. If <template> has some advantages, I'll start using it

    • spankalee 582 days ago
      <template> is special, very different from display: none; and definitely has a few advantages:

      - The elements in it are parsed into a different document and are inert until cloned or adopted into the main document. Images don't load, scripts don't run, styles don't apply, etc. This is very important.

      - The content model validation is turned off, so a <template> can contain a <td>

      - Mutation observers and events are not fired for parsed content.

      - The <template> element itself can appear anywhere, including restricted content elements like <table>

      - Other HTML processing libraries generally know to ignore <template>, unlike other content just hidden with CSS.

      This makes parsing faster and guaranteed to have no side effects, and conveys the correct semantics to the browser and tools.

      • tomhallett 582 days ago
        In addition - “template” also conveys correct semantics to the other developers working on the codebase. If I see an html at the bottom with “display: none” it’s very possible that the intended usage is to leave it there and set “display: block”, ie a modal. “display: none” doesn’t convey “clone me”, but “template” does.
      • sgc 582 days ago
        You can use templates for simpler items as indicated in the linked article, but it seems like it would be best used for larger datasets. I would usually load those async to avoid blocking the page while they download. Does it provide practical advantages over loading html to a js object via fetch?
    • Stamp01 583 days ago
      I think the main advantages are that you don't have to remember to add "display:none", and that it gives the structure more semantic meaning. If I look at your code and see "display:none", I don't know why you've chosen to hide it. If it's wrapped in a <template> element, I instantly know why it's not being displayed as I now have an idea of how it's going to be used.
      • galaxyLogic 582 days ago
        Yes but when you see "display:none;" isn't it obvious that the purpose is to make it visible sometime later?

        Also if you need to use the same HTML elsewhere, just copy the innerHTML of such an element and insert anywhere you want to.

        • xigoi 582 days ago
          > Yes but when you see "display:none;" isn't it obvious that the purpose is to make it visible sometime later?

          Yes, but it's not obvious that it would be made visible in a different place while keeping the original invisible.

        • forgotusername6 582 days ago
          Hidden forms are an example of display:none that will not be displayed later
    • miohtama 583 days ago
      If I recall correctly there are some subtle things you cannot achieve without <template>.

      <img> inside <template> is not attempted to be loaded because “it presents nothing in rendering”

      https://html.spec.whatwg.org/multipage/scripting.html#the-te...

    • runarberg 583 days ago
      > for example a dialog box

      The <dialog> element is now supported by all modern browser. I suggest you use it. It has a lot of niceties over implementing one your self, including capturing the focus, correct announcing to assistive technologies, styling the ::backdrop element, etc.

      • labrador 583 days ago
        Thanks for the tip. I should read the spec again so I don't miss these.
    • apatheticonion 583 days ago
      I think it's mostly performance as the browser can add performance optimisations for HTML but not unevaluated JavaScript.

      The alternative is a "display: none" block, but that triggers a calculation of styles, where <template> can never be rendered so it's evaluated (likely in parallel to JS) by the browser without style calculations.

      The optimisations lose weight if the <template> tag is added programatically later. For it to be maximally efficient; all of the <template> tags must be inlined in your HTML prior to your application loading. You can play around with loading it asynchronously to ensure nothing blocks.

      • spankalee 582 days ago
        Adding a <template> tag later is great for perf too because the contents are still inert and fast to clone.
    • kitsunesoba 583 days ago
      Front end web is not my forte, but as I understand display:none; can have unintended (usually negative) interactions with screen readers, which I don't believe is a possibility with HTML templates.
    • cantSpellSober 583 days ago
      <template> (along with <slot>) allows for a declarative shadow DOM instead of clunky attachShadow()s on hidden <div>s (which doesn't work server-side)

      Otherwise you probably don't need <template>, in fact they don't handle events the same as the rest of the DOM and will likely cause more pain

    • rokhayakebe 583 days ago
      I assume content within "display:none" will be read by search engines and viewed in source. Is <template> read by search engines?
      • jamesfinlayson 582 days ago
        Search engines would know to ignore the initial <template> but I'm not sure how display: none is handled.
  • lenkite 582 days ago
    I wish HTML and not just JS had supported for consuming templates. That is I wish we could do variants of:

        1. Define Template using <template id="card-template">...</template>
        2. Consume Template using <use-template id="card1" refid="card-template">..</use-template>
        
    With substitution variables/text, this feature could be amazing.
    • blowski 582 days ago
      > With substitution variables/text, this feature could be amazing.

      At which point, are you just re-creating JavaScript?

      • dspillett 582 days ago
        Not quite, I think he is taking about a more declarative approach compared to that offered by vanilla JS.
      • vbezhenar 582 days ago
        But works with disabled JS.
    • akira2501 582 days ago
      Here's what I got:

          const $template = function(template) {
              // make copy of template content
              const root = template.content.cloneNode(true);
              
              // create proxy object for accessing named nodes and root
              const obj = {
                      get $root() {
                              // after template root is called, remove this getter function
                              delete obj.$root;
                              
                              // return root only once
                              return(root);
                      }
              };
              
              // find all named template nodes and add to proxy object
              for (const node of root.querySelectorAll('[data-tmpl-name]'))
                      obj[node.dataset.tmplName] = node;
                      
              // otherwise create template node content wrapped in proxy which
              // makes attempts to overwrite properties an error.
              return(new Proxy(obj, {
                      set: () => { throw (new Error(`Attempt to overwrite a template node!`)); }
              }));
          }
          
      You can use it with something like:

          <template id="some_id">
              <div>
                  <h3 data-tmpl-name="header"></h3>
                  <h5 data-tmpl-name="title"></h5>
              </div>
              <p data-tmpl-name="info"></p>
          </template>
      
      And then just:

          function of_some_sort() {
              const t = $template(document.getElementById('some_id'));
              
              t.header.innerHTML = 'The Header';
              t.title.innerHTML = 'The title';
              
              t.info.innerHTML = 'More stuff.';
              
              document.body.append(t.$root);
          }
          
      This is was the first version I made. It's not hard to get from here to a version that can fill the template with a data object for you. In my most recent version, you could do:

          function of_another_sort() {
              document.body.append($template(document.getElementById('some_id'), {
                  header: 'The Header',
                  title:  ['first title', 'second title'],
                  
                  info: (elem) => {
                      elem.setAttribute('functions', 'work');
                      elem.innerHTML = 'and receive the internal element itself.';
                  }
              }));
          }
          
      Arrays duplicate the internal element and make copies of it, objects recurse into the element building a 'key.path.name' while looking for data-tmpl-name to replace. Functions get a copy of the element for more than just innerText/innerHTML replacement. A null or false value eliminates the internal element, and a true value passes it through unchanged.

      If you've ever used the old ruby library Amrita, it's basically that, but for HTML Template elements. You can easily do all this in about 100 lines of JS.

  • pretzelhands 582 days ago
    <template> tags are so incredibly useful for keeping your JS clean when you're not able to have something more reactive around for some reason.

    I've recently used that on an interview project I did (https://github.com/pretzelhands/ubiquitous-sniffle) and it surprisingly takes you quite far with very little effort.

    The only major annoyance is really manually keeping track of the elements in the DOM and .innerText and .innerHTML-ing everything that needs a dynamic value. But it's manageable if you keep it confined.

    • wildrhythms 582 days ago
      This approach always seems so easy at first glance, but I find it gets rather unwieldy as soon as I need to update some deeply nested value- like updating a innerText in cell in a table in a card in a layout of a thousand cards without re-rendering the entire collection. I started using lit-html and it solves this problem (and only costs me 3KB or so to ship).
    • akrymski 582 days ago
      I've used this approach in my minimal MVC lib for over a decade, simply using hidden elements instead of templates

      http://krymski.com/espresso.js/

    • ohmahjong 582 days ago
      Is the demo site still supposed to be up? I see

      > TypeError: path must be absolute or specify root to res.sendFile

      when I try to visit it.

  • Gualdrapo 583 days ago
    I'm redoing from scratch my portfolio using plain JavaScript and <template>s for most stuff, pulling everything from a JSON as a pseudo database. I think for this kind of uses <template> is great, but you can really wish it has some extra features like the ability to define repeating block sections so you didn't need to declare nested <template>s for what would be a single block code.
  • geuis 583 days ago
    Can't this be done more easily / programmatically via createDocumentFragment?

    https://developer.mozilla.org/en-US/docs/Web/API/Document/cr...

    • colejohnson66 583 days ago
      That's what the `<template>` element does. From the HTML standard[0]:

      > The template element can have template contents, but such template contents are not children of the template element itself. Instead, they are stored in a DocumentFragment associated with a different Document — without a browsing context — so as to avoid the template contents interfering with the main Document.

      [0]: https://html.spec.whatwg.org/multipage/syntax.html#template-...

  • icedchai 583 days ago
    In the jquery days, I would often do this sort of thing with hidden divs.
    • clairity 583 days ago
      i remember doing stuff like that too, but now it's official! ha.

      it's exciting to see web standards (finally) taking direction from web developers rather than corporate interests, despite chrome being so prominent. as a random aside, i'm especially hoping forms get more love, like the common behaviors (datetime entry, combobox, validation/feedback, etc.) that every developer has to wrangle with over and over. and it's great to see things like the <modal> element becoming almost fully usable without js (it still needs to be triggered via js, but can be closed with a method='dialog' form button).

      • runarberg 583 days ago
        Datetime is mostly there. I think firefox and safari have yet to implement <input type=month> and <input type=week>. As for combobox, <datalist id=mylist> and <input list=mylist> is supported everywhere. However I would like to see the CSSWG focus on better standards for styling these, it is possible now using a lot of vendor-specific hacks, but is really annoying.

        I’m gonna go ahead though and declare frontend validation (as good as) finished. the constraint validation API is amazing to work with (if you are not afraid of intercepting the submit event using JavaScript).

        • clairity 583 days ago
          yah, for datetime, i was mostly thinking about styling the widget via css (and perhaps a bit of configuration via json), with the flexibility that all those datetime components had from the jquery days. month and week are indeed just text fields in firefox (just tested it).

          and for validation, i'd like it to be js-less, as it's such an integral part of the form use case. there are recent features that help, like :user-invalid and :focus-within, but it's still far from ideal for default behavior. something as simple as styling labels based on validation state of the input is only possible with the advent of :has(), which is still incomplete/experimental in firefox, but even that's still clunky (there's a combinatorial explosion of possible states to cover, having to consider :disabled, :hover, :required, :focus, etc.).

      • recursive 583 days ago
        now? finally?

        I've been using <template> for almost a decade.

    • cantSpellSober 582 days ago
      You weren't doing the same thing, though it may have had the same effect. Template elements aren't part of the "main" DOM.
      • icedchai 582 days ago
        It had the same effect, yes, which it what I meant. The hidden div was effectively used as a template, by cloning the element and doing some awful manual data binding from JS.
  • twism 583 days ago
    Man ... I would have been thrown off a cliff if I did this in the 2000s
  • p2detar 582 days ago
    I appreciate this type of posts very much. Short, neat and to the point. I learned something today. Till now, I've always used:

      <script id="app-item-template" type="text/x-custom-template">
    
    I'll definitely have <template> in mind from now on.

    Thank you!

  • asadlionpk 583 days ago
    I wrote apps this way before React came along. I hated inserting/updating values by hand with no binding.
  • TedDoesntTalk 583 days ago
    Been doing today for years with a custom tags. name it anything you want. <foo/> works
  • kellengreen 582 days ago
    Please can we ship HTML Modules soon?

    https://chromestatus.com/feature/4854408103854080

  • chrismorgan 582 days ago
    > 'content' in document.createElement('template')

    Any reason you’d write this instead of window.HTMLTemplateElement, which is shorter to type and more efficient to evaluate?

  • quickthrower2 582 days ago
    Modulo performance, why not store the html string in JS if you are going to use JS anyway, and with string interpolation it is more templatey anyway.
    • xigoi 582 days ago
      With templates, you can do things like template.querySelector("label").
      • quickthrower2 582 days ago
        True! that would be less elegant with the string, as you would need to make it effectively into a <template> or hidden div to do such a thing.
  • Traubenfuchs 582 days ago
    What's the difference between using <template> and just putting your template content in a div with visibility:hidden?
  • asplake 582 days ago
    How does <template> differ from <script type="text/x-my-templating-lang">?
    • bugmen0t 582 days ago
      template does HTML parsing. The `content` attribute returns a DocumentFragment.
  • cookiebyt3s 582 days ago
    i didn't know templates were inert. cool!
  • lawwantsin17 582 days ago
    undefined
  • lawwantsin17 582 days ago
    undefined