Custom State Pseudo Class

Draft Community Group Report,

This version:
https://wicg.github.io/custom-state-pseudo-class/
Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google)
(Google)
Domenic Denicola (Google)

Abstract

This specification defines a way to expose custom element’s internal states, and defines the custom state pseudo class :--foo matching to a custom element exposing a state. This specification is intended to be merged to [HTML] and [selectors-4] in the future.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

1.1. Motivation

Build-in elements provided by user agents have certain “states” that can change over time depending on user interaction and other factors, and are exposed to web authors through pseudo classes. For example, some form controls have the “invalid” state, which is exposed through the :invalid pseudo-class.

Like built-in elements, custom elements can have various states to be in too, and custom element authors want to expose these states in a similar fashion as the built-in elements.

1.2. Solution

This specification defines an API to inform custom element's states to the user agent, and a pseudo-class to select elements with specific states. The former is the states IDL attribute of ElementInternals, and the latter is the custom state pseudo class.

Assume that LabeledCheckbox doesn’t expose its "checked" state via a content attribute.

<!DOCTYPE html>
<body>
<!-- Basic usage: -->
<script>
class LabeledCheckbox extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
    this.addEventListener('click', this._onClick.bind(this));

    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML =
      `<style>
       :host::before {
         content: '[ ]';
         white-space: pre;
         font-family: monospace;
       }
       :host(:--checked)::before { content: '[x]' }
       </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) {
    this.checked = !this.checked;
  }
}

customElements.define('labeled-checkbox', LabeledCheckbox);
</script>

<style>
labeled-checkbox { border: dashed red; }
labeled-checkbox:--checked { border: solid; }
</style>

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

<script>
class QuestionBox extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML =
      `<div><slot>Question</slot></div>
       <labeled-checkbox part='checkbox'>Yes</labeled-checkbox>`;
  }
}
customElements.define('question-box', QuestionBox);
</script>

<style>
question-box::part(checkbox) { color: red; }
question-box::part(checkbox):--checked { color: green; }
</style>

<question-box>Continue?</question-box>
</body>

2. Exposing custom element states

Each autonomous custom element has states set, a set of strings, and the the custom element is initially associated with an empty set of strings.

Support customized built-in elements. <https://github.com/whatwg/html/issues/5166>

partial interface ElementInternals {
  readonly attribute CustomStateSet states;
};

[Exposed=Window]
interface CustomStateSet {
  setlike<DOMString>;
  undefined add(DOMString value);
};

The states IDL attribute returns the states set of this’s target element.

The add(value) method, when invoked, must run these steps:

  1. If value does not match to <dashed-ident>, then throw a "SyntaxError" DOMException.

  2. Invoke the default add operation, which the setlike<DOMString> would have if CustomStateSet interface had no add(value) operation, with value arguemnt.

States set can expose boolean states represented by existence/non-existence of string values. If an author wants to expose a state which can have three values, it can be converted to three exclusive boolean states. For example, a state called readyState with "loading", "interactive", and "complete" values can be mapped to three exclusive boolean states, "--loading", "--interactive", and "--complete".
// Change the readyState from anything to "complete".
this._readyState = "complete";
this._internals.states.delete("--loading");
this._internals.states.delete("--interactive");
this._internals.states.add("--complete");

Support non-boolean states. <https://github.com/WICG/custom-state-pseudo-class/issues/4>

3. Selecting a custom element with a specific state

The custom state pseudo class :--foo is a pseudo-class, and applies while an element has a certain state. "State" is a per-element information which can change over time depending on user interaction and other extrinsic factors. The custom state pseudo class must start with :, followed by one <dashed-ident>, otherwise the selector is invalid.

The custom state pseudo class must match any element that is an autonomous custom element and whose states set contains the specified <dashed-ident>.

A custom state pseudo class contains just one <dashed-ident>, and an element can expose multiple states. Authors can use custom state pseudo class with logical pseudo-classes like x-foo:is(:--state1, :--state2), x-foo:not(:--state2), and x-foo:--state1:--state2.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4. URL: https://drafts.csswg.org/selectors/
[WebIDL]
Boris Zbarsky. Web IDL. URL: https://heycam.github.io/webidl/

IDL Index

partial interface ElementInternals {
  readonly attribute CustomStateSet states;
};

[Exposed=Window]
interface CustomStateSet {
  setlike<DOMString>;
  undefined add(DOMString value);
};

Issues Index

Support customized built-in elements. <https://github.com/whatwg/html/issues/5166>
Support non-boolean states. <https://github.com/WICG/custom-state-pseudo-class/issues/4>