codepleb
codepleb

Reputation: 10571

Dynamic tags for lit-html not possible?

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.

Upvotes: 13

Views: 7273

Answers (3)

Justin Fagnani
Justin Fagnani

Reputation: 11171

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.

Upvotes: 10

Gh61
Gh61

Reputation: 9746

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)}>`;
  }
}

Upvotes: 12

codepleb
codepleb

Reputation: 10571

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)}
    `;

Upvotes: 7

Related Questions