Search code examples
htmlcssweb-componentcustom-componentshadow-dom

CSS counter not incrementing in shadow DOM


I have the following setup where a CSS counter works for slotted content but not in the shadow DOM.

import { LitElement, css, html } from 'lit-element';

class MyElement extends LitElement {

 static get properties() {
    return {
      counter: { type: Number },
    };
  }

  render() {
    return html`
      <div><slot></slot></div>
      <div class="foo">
        <h1>Hey</h1>
        <h1>Ho</h1>
      </div>
    `;
  }
}

MyElement.styles = [
  css`
    :host {
      counter-reset: partCounter afterCounter;
    }
    :host ::slotted(*):before {
      counter-increment: partCounter;
      content: 'Slotted ' counter(partCounter) '. ';
    }
    h1:after {
      counter-increment: afterCounter;
      content: ' Shadow ' counter(afterCounter) '. ';
    }
  `,
];
customElements.define('my-element', MyElement);
<my-element>
  <h1>one</h1>
  <h1>two</h1>
</my-element>

I see this output: Shadow 1 Shadow 1. Expected output: Shadow 1 Shadow 2.

Why is it behaving this way? I'm more interested in an explanation why, though a solution would be nice as well.

Working demo on Codesandbox: https://codesandbox.io/s/4j6n7xwmj7

P.S.: Some hints in this Github thread, but to me it suggests that it should actually be working: https://github.com/w3c/csswg-drafts/issues/2679


Solution

  • It is all in where you place the counter-reset.

    :host is needed for thing inside a slot and, in this case I added the other into .foo.

    You can see from the example below that it works fine.

    Yes, I removed all of LIT, but the principle is the same with or without LIT.

    class MyElement extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({mode:'open'}).innerHTML = `
          <style>
          :host {
            counter-reset: partCounter;
          }
          :host ::slotted(*):before {
            counter-increment: partCounter;
            content: 'Slotted ' counter(partCounter) ': ';
          }
          .foo {
            counter-reset: afterCounter;
          }
          h1:before {
            counter-increment: afterCounter;
            content: ' Shadow ' counter(afterCounter) ' - ';
          }
          </style>
          <div><slot></slot></div>
          <div class="foo">
            <h1>Hey</h1>
            <h1>Ho</h1>
          </div>
        `;
      }
    }
    
    customElements.define('my-element', MyElement);
    <my-element>
      <h1>one</h1>
      <h1>two</h1>
    </my-element>

    To see that each works independently I changed it to the following:

    class MyElement extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({mode:'open'}).innerHTML = `
          <style>
          :host {
            counter-reset: partCounter -10;
          }
          :host ::slotted(*):before {
            counter-increment: partCounter;
            content: 'Slotted ' counter(partCounter) ': ';
          }
          .foo {
            counter-reset: afterCounter 30;
          }
          h1:before {
            counter-increment: afterCounter;
            content: ' Shadow ' counter(afterCounter) ' - ';
          }
          </style>
          <div><slot></slot></div>
          <div class="foo">
            <h1>Hey</h1>
            <h1>Ho</h1>
          </div>
        `;
      }
    }
    
    customElements.define('my-element', MyElement);
    <my-element>
      <h1>one</h1>
      <h1>two</h1>
    </my-element>