Clarifying the status of custom attributes in HTML Custom Elements in WebComponents

439 views Asked by At

Until recently, whenever I've needed a custom attribute in my HTML, I've always used an HTML data-* custom attribute.

Having recently started experimenting with WebComponents and, specifically, Custom Elements, I have started thinking in terms of custom attributes which are not HTML5 data-* custom attributes.

Before inadvertently adopting any non-recommended practices, I would like to clarify the following...

In the list below we have 4 elements:

  • Element i is a standard element with a data-* attribute
  • Element ii is a standard element with a custom attribute
  • Element iii is a custom element with a data-* attribute
  • Element iv is a custom element with a custom attribute

const toggleDataAttribute = (e) => {
    e.target.dataset.inverted = (e.target.dataset.inverted === 'true') ? 'false' : 'true';
}

const toggleCustomAttribute = (e) => {
  if (e.target.getAttribute('inverted') === 'true') {
    e.target.setAttribute('inverted', 'false');
  }

  else {
    e.target.setAttribute('inverted', 'true');
  }
}

const toggleInvert = (e) => {

  if (e.target.dataset.inverted) {
    toggleDataAttribute(e);
  }
  
  else {
    toggleCustomAttribute(e);
  }
}


// Attach click event TO <div> elements
let divs = [...document.getElementsByTagName('div')];

divs.forEach((div) => div.addEventListener('click', toggleInvert, false));

// Attach click event TO <my-circle> elements
let myCircles = [...document.getElementsByTagName('my-circle')];

myCircles.forEach((myCircle) => myCircle.addEventListener('click', toggleInvert, false));


// Define <my-circle> element
class myCircle extends HTMLElement {
  
  constructor() {
    super();
    this.root = this.attachShadow({mode: "open"});
  }

  connectedCallback() {
    this.root.appendChild(document.createElement('slot'));
  }
}

customElements.define('my-circle', myCircle);
aside {
  position: absolute;
  top: 0;
  right: 0;
  width: 280px;
  line-height: 24px;
}

div {
  float: left;
  margin: 0 12px 12px 0;
  width: 80px;
  height: 80px;
  line-height: 80px;
  text-align: center;
  font-size: 36px;
  border-radius: 50%;
  cursor: pointer;
}

my-circle {
  display: block;
  float: left;
  margin: 0 12px 12px 0;
  width: 80px;
  height: 80px;
  line-height: 80px;
  text-align: center;
  font-size: 36px;
  background: radial-gradient(#fff, #000);
  border-radius: 50%;
  cursor: pointer;
}

my-circle:first-of-type {
  clear: left;
}


div:nth-of-type(1) {
  background: radial-gradient(rgb(255, 255, 0), rgb(255, 0, 0));
}

div:nth-of-type(2) { 
  background: radial-gradient(rgb(255, 255, 0), rgb(0, 163, 0));
}

my-circle:nth-of-type(1) { 
  background: radial-gradient(rgb(255, 255, 0), rgb(223, 163, 0));
}

my-circle:nth-of-type(2) {
  background: radial-gradient(rgb(255, 127, 127), rgb(255, 0, 0));
}

div[data-inverted="true"],
div[inverted="true"],
my-circle[data-inverted="true"],
my-circle[inverted="true"] {
  filter: hue-rotate(180deg);
}
<div data-inverted="false">i</div>
<div inverted="false">ii</div>

<my-circle data-inverted="false">iii</my-circle>
<my-circle inverted="false">iv</my-circle>

<aside>
<p><strong>Click</strong> on each of the circles on the left to invert their backgrounds.</p>
</aside>

Although the set up above works technically, which of the following is true:

  • A) Custom attributes may be used universally, in standard elements and custom elements.

    • Conclusion: Elements i, ii, iii & iv are all valid
  • B) Custom attributes may only be used in custom elements. They are invalid elsewhere.

    • Conclusion: Elements i, iii & iv are valid, while ii is invalid
  • C) Data-* attributes are for standard elements, custom attributes are for custom elements.

    • Conclusion: Elements i & iv are valid, while ii & iii are invalid
  • D) Custom attributes are not even a thing. Where did you get this idea from?

    • Conclusion: Elements i & iii are valid, while ii & iv are invalid

Added:

To illustrate my question above, I'd like to give an example of where custom attributes appear not to be valid:

  1. Go to: https://validator.w3.org/nu/#textarea

  2. Select text input

  3. Enter:

<!DOCTYPE html>
<html lang="">
<head>
<title>Test</title>
</head>
<body>

<div data-inverted="false">i</div>

<div inverted="false">ii</div>

</body>
</html>
  1. Check the markup

The validator returns the error:

Error: Attribute inverted not allowed on element div at this point.

From line 10, column 1; to line 10, column 22

i</div>↩↩<div inverted="false">ii</di

Though... I'm not sure if the tool at https://validator.w3.org/nu/ is outdated and / or abandoned and the Error returned should no longer be regarded as an error in 2020 (?)

1

There are 1 answers

11
Danny '365CSI' Engelman On

All 4 usages work, so why should they be invalid?

data- prefix gives the added bonus they are available in element.dataset.

-- Attributes are Attributes -- , nothing special in the Custom Elements API,
apart from observedAttributes(). Yes, you can use data-* attributes there to.

note

class myCircle extends HTMLElement {
  constructor() {
    super();
    this.root = this.attachShadow({mode: "open"});
  }
  connectedCallback() {
    this.root.appendChild(document.createElement('slot'));
  }
}

can be written as:

class myCircle extends HTMLElement {
  constructor() {
    super()
      .attachShadow({mode: "open"})
      .append(document.createElement('slot'));
  }
}

because super() returns 'this'
and attachShadow both sets and returns this.shadowRoot for free
you are not doing anything with appendChild() return value, so append() (which can take multiple parameters) is enough.

Also note there is a toggleAttribute method.