Dynamic tags for lit-html not possible?
Asked Answered
M

3

13

Can anyone tell me why I cannot use variables within lit-html's html method?

const h1 = 'h1';
return html`
  <${h1} class="a-heading ${classes}">
    <slot></slot>
  </${h1}>
`;

If I replace ${h1} with h1 that works without problems.

Magness answered 9/1, 2020 at 14:49 Comment(3)
ask them! github.com/polymer/lit-html/issuesGrooms
Does this answer your question? Is there a way to make tags in lit-html templates dynamic?Proudhon
@AlanDávalos At first I thought 'yes', because it says this is not possible. But a working colleague just helped me out.Magness
M
7

For everyone interested in my solution: Use unsafeHTML if you can (you should not do that if you wrap any input fields within).

    import { unsafeHTML } from 'lit-html/directives/unsafe-html';
   
     // ...

    const template = `
      <h${this.rank} class="a-heading">
        <slot></slot>
      </h${this.rank}>
    `;

    return html`
      ${unsafeHTML(template)}
    `;
Magness answered 10/1, 2020 at 14:12 Comment(1)
This is no longer recommended approach - use literal or unsafeStatic as shown in the other answer.Altamirano
A
12

It's 2022 and the solution exists (from the end of 2020 - Issue).

Now you can use something from https://lit.dev/docs/templates/expressions/#static-expressions

  1. For string literals, that you have control of, use literal template.

    You must import special version of html and use the literal template.

import {html, literal} from 'lit/static-html.js';

@customElement('my-button')
class MyButton extends LitElement {
  tag = literal`button`;
  activeAttribute = literal`active`;
  @property() caption = 'Hello static';
  @property({type: Boolean}) active = false;

  render() {
    return html`
      <${this.tag} ${this.activeAttribute}?=${this.active}>
        <p>${this.caption}</p>
      </${this.tag}>`;
  }
}

and then

@customElement('my-anchor')
class MyAnchor extends MyButton {
  tag = literal`a`;
}
  1. For something, that you cannot decorate using the literal, you can use unsafeStatic.

    Again, you must import special version of html and then use unsafeStatic function.

import {html, unsafeStatic} from 'lit/static-html.js';

@customElement('my-button')
class MyButton extends LitElement {
  @property() caption = 'Hello static';
  @property({type: Boolean}) active = false;

  render() {
    // These strings MUST be trusted, otherwise this is an XSS vulnerability
    const tag = getTagName();
    const activeAttribute = getActiveAttribute();
    return html`
      <${unsafeStatic(tag)} ${unsafeStatic(activeAttribute)}?=${this.active}>
        <p>${this.caption}</p>
      </${unsafeStatic(tag)}>`;
  }
}
Annorah answered 16/10, 2022 at 16:1 Comment(4)
I didn't verify that this works, but this looks like the thing I wanted back then! Thanks for posting this.Magness
Thank you. It is working, I am using it. But it took me way too long until I find out about this feature. I think some people like me, who stumble across this question, will find this useful.Annorah
But this solution doesn't allow me add in other template with html, is there a workaround?Gave
Using import {html} from 'lit/static-html.js'; instead import { html } from "lit"; helped me. Thanks!Else
R
10

Update: Thanks @Gh61 for a current answer!

This answer is no longer correct. lit-html 2.0+ has a static template function that accepts bindings in any position

The reason lit-html doesn't allow for dynamic tag names is that lit-html works by replacing the expressions with special markers and then creating an HTML <template> element with the result.

The key, and slightly subtle, part here is that it does not use the values to create the template. They are interpolated into the template after the template is cloned, which is after the HTML has ben parsed. There's no way to go into a tree of DOM and change the tag name of one element. We'd have to remove the element, replace it, set up any bindings, and move any children into the new element. This would be very expensive.

We do have plans to support static bindings (once we can drop support for older Edge browsers that don't implement template literals quite correctly) which are interpolated before creating the HTML <template>, which would allow for using expressions for tag names. Static bindings would not be updatable with new data however - the value at template creation time is the only value used.

Richardo answered 21/1, 2020 at 2:15 Comment(1)
Consider updating (or deleting) the answer in favor of current literal and unsafeStatic solutions.Altamirano
M
7

For everyone interested in my solution: Use unsafeHTML if you can (you should not do that if you wrap any input fields within).

    import { unsafeHTML } from 'lit-html/directives/unsafe-html';
   
     // ...

    const template = `
      <h${this.rank} class="a-heading">
        <slot></slot>
      </h${this.rank}>
    `;

    return html`
      ${unsafeHTML(template)}
    `;
Magness answered 10/1, 2020 at 14:12 Comment(1)
This is no longer recommended approach - use literal or unsafeStatic as shown in the other answer.Altamirano

© 2022 - 2024 — McMap. All rights reserved.