CustomStateSet
==============

**Experimental:** **This is an experimental technology**  
Check the Browser compatibility table carefully before using this in production.

The **`CustomStateSet`** interface of the Document Object Model stores a list of states for an autonomous custom element, and allows states to be added and removed from the set.

The interface can be used to expose the internal states of a custom element, allowing them to be used in CSS selectors by code that uses the element.

Instance properties
-------------------

`CustomStateSet.size` 
Experimental

Returns the number of values in the `CustomStateSet`.

Instance methods
----------------

`CustomStateSet.add()` 
Experimental

Adds a value to the set, first checking that the *value* is a `<dashed-ident>`.

`CustomStateSet.clear()` 
Experimental

Removes all elements from the `CustomStateSet` object.

`CustomStateSet.delete()` 
Experimental

Removes one value from the `CustomStateSet` object.

`CustomStateSet.entries()` 
Experimental

Returns a new iterator with the values for each element in the `CustomStateSet` in insertion order.

`CustomStateSet.forEach()` 
Experimental

Executes a provided function for each value in the `CustomStateSet` object.

`CustomStateSet.has()` 
Experimental

Returns a `Boolean` asserting whether an element is present with the given value.

`CustomStateSet.keys()` 
Experimental

An alias for `CustomStateSet.values()`.

`CustomStateSet.values()` 
Experimental

Returns a new iterator object that yields the values for each element in the `CustomStateSet` object in insertion order.

Description
-----------

 Built in HTML elements can have different *states*, such as "enabled" and "disabled, "checked" and "unchecked", "initial", "loading" and "ready".
 Some of these states are public and can be set or queried using properties/attributes, while others are effectively internal, and cannot be directly set.
 Whether external or internal, element states can generally be selected and styled using CSS pseudo-classes as selectors.

 The `CustomStateSet` allows developers to add and delete states for autonomous custom elements (but not elements derived from built-in elements).
 These states can then be used as custom state pseudo-class selectors in a similar way to the pseudo-classes for built-in elements.

### Setting custom element states

 To make the `CustomStateSet` available, a custom element must first call `HTMLElement.attachInternals()` in order to attach an `ElementInternals` object.
 `CustomStateSet` is then returned by `ElementInternals.states`.
 Note that `ElementInternals` cannot be attached to a custom element based on a built-in element, so this feature only works for autonomous custom elements (see github.com/whatwg/html/issues/5166).

 The `CustomStateSet` instance is a `Set`-like object that can hold an ordered set of state values.
 Each value is a dashed identifier, with the format: `--mystatename`.
 Identifiers can be added to the set or deleted.
 If an identifier is present in the set the particular state is `true`, while if it is removed the state is `false`.

Custom elements that have states with more than two values can represent them with multiple boolean states, only one of which is `true` (present in the `CustomStateSet`) at a time.

The states can be used within the custom element but are not directly accessible outside of the custom component.

### Interaction with CSS

 Developers can select a custom element with a specific state using its state *custom state pseudo-class*.
 The format of this pseudo-class is `:--mystatename`, where `--mystatename` is the state as defined in the element.

The custom state pseudo-class matches the custom element only if the state is `true` (i.e. if `--mystatename` is present in the `CustomStateSet`).

Examples
--------

### Labeled Checkbox

 This example, which is adapted from the specification, demonstrates a custom checkbox element that has an internal "checked" state.
 This is mapped to the `--checked` custom state, allowing styling to be applied using the `:--checked` custom state pseudo class.

#### JavaScript

 First we define our class `LabeledCheckbox` which extends from `HTMLElement`.
 In the constructor we call the `super()` method, leaving most of the "work" to `connectedCallback()`, which is invoked when a custom element is added to the page.
 The content of the element is defined using a `<style>` element to be the text `[]` or `[x]` followed by a label.
 What's noteworthy here is that the custom state pseudo class is used to select the text to display: `:host(:--checked)::`.
 After the example below, we'll cover what's happening in the snippet in more detail.


```js
class LabeledCheckbox extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    // Attach an ElementInternals to get states property
    this._internals = this.attachInternals();
    this.addEventListener("click", this.\_onClick.bind(this));

    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<style>
 :host {
 display: block;
 }
 :host::before {
 content: '[ ]';
 white-space: pre;
 font-family: monospace;
 }
 :host(:--checked)::before { content: '[x]'; background: grey; }
 </style>
 <slot>Label</slot>`;
  }

  get checked() {
    return this._internals.states.has("--checked");
  }

  set checked(flag) {
    if (flag) {
      this._internals.states.add("--checked");
    } else {
      this._internals.states.delete("--checked");
    }
  }

  \_onClick(event) {
    // Toggle the 'checked' property when the element is clicked
    this.checked = !this.checked;
  }
}
```


In the `LabeledCheckbox` class:

* The `connectedCallback()` method uses ``this.attachInternals()`` to attach an ``ElementInternals`` object.
* In the `get checked()` and `set checked()` we use `ElementInternals.states` to get the `CustomStateSet`.
* The `set checked(flag)` method adds the `"--checked"` dashed identifier to the `CustomStateSet` if the flag is set and delete the identifier if the flag is `false`.
* The `get checked()` method just checks whether the `--checked` property is defined in the set.
* The property value is toggled when the element is clicked.

We then call the `define()` method on the object returned by `Window.customElements` in order to register the custom element:


```js
customElements.define("labeled-checkbox", LabeledCheckbox);
```


#### HTML

After registering the custom element we can use the element in HTML as shown:


```html
<labeled-checkbox>You need to check this</labeled-checkbox>
```


#### CSS

Finally we use the `:--checked` custom state pseudo class to select CSS for when the box is checked.


```css
labeled-checkbox {
  border: dashed red;
}
labeled-checkbox:--checked {
  border: solid;
}
```


#### Result

Click the element to see a different border being applied as the checkbox `checked` state is toggled.

### Non-boolean internal states

This example shows how to handle the case where the custom element has an internal property with multiple possible value.

 The custom element in this case has a `state` property with allowed values: "loading", "interactive" and "complete".
 To make this work, we map each value to its custom state and create code to ensure that only the dashed identifier corresponding to the internal state is set.
 You can see this in the implementation of the `set state()` method: we set the internal state, add the dashed identifier for the matching custom state to `CustomStateSet`, and remove the dashed identifiers associated with all the other values.

Most of the remaining code is similar to the example that demonstrates a single boolean state (we show different text for each state as the user toggles through them).

#### JavaScript


```js
class ManyStateElement extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    // Attach an ElementInternals to get states property
    this._internals = this.attachInternals();
    this.state = "loading";
    this.addEventListener("click", this.\_onClick.bind(this));

    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<style>
 :host {
 display: block;
 font-family: monospace;
 }
 :host::before { content: '[ unknown ]'; white-space: pre; }
 :host(:--loading)::before { content: '[ loading ]' }
 :host(:--interactive)::before { content: '[ interactive ]' }
 :host(:--complete)::before { content: '[ complete ]' }
 </style>
 <slot>Click me</slot>`;
  }

  get state() {
    return this._state;
  }

  set state(stateName) {
    // Set internal state to passed value
    // Add dashed identifier matching state and delete others
    if (stateName == "loading") {
      this._state = "loading";
      this._internals.states.add("--loading");
      this._internals.states.delete("--interactive");
      this._internals.states.delete("--complete");
    } else if (stateName == "interactive") {
      this._state = "interactive";
      this._internals.states.delete("--loading");
      this._internals.states.add("--interactive");
      this._internals.states.delete("--complete");
    } else if (stateName == "complete") {
      this._state = "complete";
      this._internals.states.delete("--loading");
      this._internals.states.delete("--interactive");
      this._internals.states.add("--complete");
    }
  }

  \_onClick(event) {
    // Cycle the state when element clicked
    if (this.state === "loading") {
      this.state = "interactive";
    } else if (this.state === "interactive") {
      this.state = "complete";
    } else if (this.state === "complete") {
      this.state = "loading";
    }
  }
}

customElements.define("many-state-element", ManyStateElement);
```


#### HTML

 After registering the new element we add it to the HTML.
 This is similar to the example that demonstrates a single boolean state, except we don't specify a value and use the default value from the slot (`<slot>Click me</slot>`).


```html
<many-state-element></many-state-element>
```


#### CSS

 In the CSS we use the three custom state pseudo classes to select CSS for each of the internal state values: `:--loading`, `:--interactive`, `:--complete`.
 Note that the custom element code ensures that only one of these custom states can be defined at a time.


```css
many-state-element:--loading {
  border: dotted grey;
}
many-state-element:--interactive {
  border: dashed blue;
}
many-state-element:--complete {
  border: solid green;
}
```


#### Results

Click the element to see a different border being applied as the state changes.

Specifications
--------------

| Specification |
| --- |
| Custom State Pseudo Class # customstateset |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

Using custom elements