CSS tem pseudoclasses para estilizar elementos HTML com base no estado – há :invalid
, :hover
, :checked
, :placeholder-shown
, etc. De forma semelhante, com a CustomStateSet
API, os componentes da web podem expor suas próprias pseudoclasses como :state(loading)
, :state(done)
, etc.
Suporte ao navegador
Safari 17.4 adicionou suporte para a API CustomStateSet. Também está disponível no Firefox Nightly. O Chrome suportava uma sintaxe obsoleta mais antiga. O Chrome suporta a nova sintaxe a partir da versão 125.
Vejamos um exemplo: um botão que copia texto para a área de transferência do usuário. O usuário clica no botão, o texto é copiado e o botão exibe temporariamente uma confirmação de que a ação foi bem-sucedida. Enquanto a confirmação estiver sendo exibida, direi que o elemento personalizado está em seu estado de confirmação .
Ser capaz de estilizar com base no estado é útil tanto para o criador quanto para o consumidor do elemento personalizado. Nos exemplos abaixo, o autor do elemento personalizado está usando o estado para alterar o ícone de um símbolo de cópia para um símbolo de escala. O usuário do elemento personalizado está usando o estado para personalizar o background-color
tom de verde de sua preferência.
Estilizando de dentro do shadow DOM:
:host(:state(confirmation)) button {
background-image: url("/tick.svg");
}
Estilizando fora do shadow DOM:
copy-button:state(confirmation) {
--bg: #34d399;
}
O seletor de estado personalizado pode ser combinado ::part
para selecionar uma parte específica do elemento personalizado para estilizar fora do shadow DOM:
copy-button:state(confirmation)::part(button) {
background-color: #34d399;
}
Essa é a sintaxe CSS, mas como configuramos isso?
Adicionando estado a um elemento personalizado
O .attachInternals()
método retorna um ElementInternals
objeto. ElementInternals
tem uma .states
propriedade que retorna um CustomStateSet
objeto.
class CopyButton extends HTMLElement {
#internals = this.attachInternals();
#states = this.#internals.states;
// more code...
}
Ao usar a sintaxe do campo de classe privada ( #
), o estado não pode ser alterado por código fora da classe.
CustomStateSet
tem um método .add()
and .delete()
para adicionar e excluir estados.
const styles = new CSSStyleSheet();
styles.replaceSync(`
button {
background-color: var(--bg, rgb(240,240,240));
background-image: url(/copy.svg);
}
:host(:state(confirmation)) button {
background-image: url("/tick.svg");
}`);
class CopyButton extends HTMLElement {
#internals = this.attachInternals();
#states = this.#internals.states;
#shadowRoot = this.attachShadow({ mode: "open" });
connectedCallback() {
this.addEventListener("click", this.#onClick.bind(this));
this.#shadowRoot.adoptedStyleSheets.push(styles);
this.#shadowRoot.innerHTML = `<button part="button"></button>`;
}
get confirmation() {
return this.#states.has("confirmation");
}
#onClick(event) {
navigator.clipboard.writeText(this.getAttribute("value"));
this.#states.add("confirmation");
setTimeout(() => {
this.#states.delete("confirmation");
}, 2000);
}
}
customElements.define("copy-button", CopyButton);
Selecionando componentes com base no estado personalizado comquerySelector
Você não apenas pode estilizar componentes com base no estado, mas também selecionar componentes com JavaScript com base no estado. querySelector
e querySelectorAll
aceite qualquer seletor CSS válido como argumento, para que possamos usar a mesma sintaxe. Se você criar um <toggle-button>
componente da web com um estado selecionado personalizado , por exemplo, o seguinte selecionaria apenas os elementos nesse estado:
const selectedButtons = document.querySelectorAll('toggle-button:state(selected)');