Aguarde...

28 de fevereiro de 2023

O potencial desperdiçado dos seletores de atributo CSS

O potencial desperdiçado dos seletores de atributo CSS

Sou usuário antigo do BEM. O sublinhado duplo aparece no meu código mesmo quando a colisão do seletor não é um problema. Não acho que seja o BEM, especificamente, que me atrai (por acaso foi a metodologia que aprendi pela primeira vez e com a qual me acostumei). É o fato de que uma metodologia, qualquer metodologia, adiciona estrutura e semântica à nomenclatura de classe HTML onde inerentemente não há.

É por isso que sempre me sinto um pouco perdido ao usar módulos CSS ou bibliotecas CSS-in-JS. O que quer dizer com posso nomear minhas classes ou componentes estilizados como eu quiser? Não é incomum que as pessoas usem BEM com módulos CSS. Isso pode parecer bobo – eles resolvem o mesmo problema, afinal. Mas talvez isso mostre que convenções rígidas de nomenclatura de classe são atraentes além de seu poder de evitar colisões. Para o bem ou para o mal, eles nos permitem avaliar o papel de cada seletor apenas a partir da folha de estilo.

Em nossa preocupação com as classes, é fácil pensar nelas como o seletor padrão. Historicamente, quando as pessoas falam sobre direcionar elementos por seus atributos, é no contexto de truques legais ou frases curtas. Você faz uma anotação mental para usar a[href^=https://specific-domain.com]na próxima vez que quiser usar sublinhados rosa para cada link para um domínio específico e imediatamente esquece isso para sempre. Ou, para dar um exemplo mais prático, os seletores de atributo têm sido recomendados como uma espécie de linter ou depurador para HTML inválido ( img:not([alt]) { border: 1rem solid red; }).

Mais recentemente, a ideia de tratar seletores de atributos em pé de igualdade com classes como cidadãos de primeira classe foi proposta de forma mais ampla. Não estamos mais falando sobre casos extremos, mas desafiando a própria padronização das classes, sem abrir mão do senso de estrutura que muitos de nós procuramos nas convenções de nomenclatura CSS.

Aleksandr e Keith defendem seletores de atributo em vez de nomes de classe, mas enquanto Aleksandr descreve a abordagem mais conservadora de preferir seletores de atributo onde já existe um no elemento HTML, Keith vai um passo além e propõe adicionar seu próprio atributo em vez de outra classe data-*quando você precisa de algo para se conectar.

Vejamos ambos mais de perto.

Representando o estado nativo

Aleksandr Hovhannisyan argumenta que as classes são usadas com muita frequência para estilizar diferentes estados ou variantes de um elemento, onde esse estado pode – ou, criticamente, deveria  - já ser semanticamente representado por outras propriedades, como atributos ARIA ou pseudoseletores.

Nenhum objeto que você deva preferir input[disabled]em vez de input.is-disabled, porque na maioria das vezes é desnecessário adicionar esse nome de classe manualmente quando você já tem um seletor de atributos para conectar. Mas tendemos a esquecer que existem muitas outras formas nativas de representar o estado que deveríamos usar de qualquer maneira – ou seja, atributos ARIA. (Embora não os obtenhamos de graça, como fazemos com pseudoclasses como  :hover,  :checkedou  :focus.)

Compare o seguinte. Usando classes modificadoras:

<a href="/page2/" class="nav-link nav-link--is-active">Page 2</a>

<style>
.nav-link.nav-link--is-active { }
</style>

Usando atributos ARIA:

<a href="/page2/" class="nav-link" aria-current="page">Page 2</a>

<style>
.nav-link[aria-current="page"] { }
</style>

O ponto crucial aqui é que aria-current="page"deve ser incluído independentemente das necessidades de estilo de um link ativo, e não consigo pensar em um motivo pelo qual devemos adicionar uma classe extra onde o estado já pode ser selecionado de uma maneira diferente. Na pior das hipóteses, isso significaria que você está implementando o gerenciamento de estado duas vezes.

Isso promove uma mentalidade de a11y-first — se não houver nenhum atributo ou pseudoseletor disponível para representar o estado que desejamos estilizar, devemos adicionar um? Estamos usando o elemento HTML correto? Somos forçados a passar por um fluxograma mental de recursos nativos e semânticos de HTML e CSS que poderíamos explorar antes de recorrer às aulas.

Lembro-me do princípio orientador da Testing Library de apenas alcançar o testIdseletor de elemento quando você descartou todas as outras opções de consulta mais semânticas. E se tratássemos as classes um pouco como IDs de teste, apreciando-as como uma opção de fallback robusta e genérica que podemos alcançar quando não há outros atributos semânticos apropriados disponíveis no elemento?

Representando o estado personalizado

Aleksandr Hovhannisyan explica bem como o uso de seletores de atributos pode resultar em melhor UX. Acho uma coincidência satisfatória que Keith Cirkel chegue às mesmas conclusões vindo de um local de experiência de desenvolvedor. Ele destaca a questão do DX com as classes:

As classes, sendo uma lista de strings arbitrárias, não têm valores-chave, nenhum estado privado, nenhum tipo complexo (o que também significa que o suporte IDE é bastante limitado) e dependem de DSLs personalizados como BEM apenas para torná-los um pouco mais utilizáveis. Continuamos tentando implementar parâmetros em um Set<string>quando o que queremos é um arquivo Map<string, T>.

Existe um princípio fundamental bem conhecido na engenharia de software: torne os estados impossíveis impossíveis 1 .

Eu recomendo fortemente o artigo e a palestra vinculada. Esse pode ser o princípio em que mais confio ao criar código e a principal razão pela qual lamento a falta de enums nativos em JavaScript. 

Minha impressão do artigo de Keith é que, em sua essência, este é o princípio que os seletores de classe violam. Nunca é garantido que as classes de um elemento reflitam seu estado (nada impede que você adicione a classe button--loadinga um botão que não está, de fato, carregando) e as classes usadas para representar uma das muitas variantes mutuamente exclusivas nunca têm garantia de serem mutuamente exclusivos (nada impede que você adicione button--primarybutton--secondaryao mesmo botão).

Eu amo como Keith enquadra isso como um problema de tipo. Como exemplo, podemos observar a nomenclatura de algumas das classes utilitárias do Tailwind: o uso de dois pontos para agrupar conjuntos de classes relacionados pode ser interpretado como uma tentativa de sinalizar exclusividade mútua. <div class="sm:w-80 sm:w-96">deve soar o alarme porque a lógica ofensiva é imediatamente visível na nomeação.

Keith nos lembra que os seletores de atributos podem ser alguns dos mais versáteis em nosso cinto de ferramentas CSS — os  data-*atributos, combinados com a rica variação nos seletores de atributos CSS , podem ser usados ​​para capturar strings, bem como listas — e ele nos encoraja a adotá-los. Aqui reside a diferença entre as sugestões dele e de Aleksandr: não se trata apenas de usar os atributos nativos já presentes no elemento, mas de adicionar atributos personalizados — em vez de classes — quando não estão.

Um exemplo do artigo de Keith:

.Card { /* ... */ }
.Card[data-size=big] { width: 100%; }
.Card[data-size=medium] { width: 50%; }
.Card[data-size=small] { width: 25%; }

.Card[data-align=left] { text-align: left; }
.Card[data-align=right] { text-align: right; }
.Card[data-align=center] { text-align: center; }
.Card[data-border-collapse~="top"] { border-top: 0 }
.Card[data-border-collapse~="right"] { border-right: 0 }
.Card[data-border-collapse~="bottom"] { border-bottom: 0 }
.Card[data-border-collapse~="left"] { border-left: 0 }

É muito mais fácil evitar estados impossíveis quando você tem diferentes “slots” para preencher com os diferentes grupos de variantes para o seu elemento, em vez de enfiar todos eles em uma única sequência de paralelepípedos em , por mais disciplinada que seja sua nomenclatura class.

E há uma razão pela qual <article class="card" data-align="left" data-size="small" />parece atraente – é espelhar as APIs que estamos acostumados a ver em sistemas de design e bibliotecas de componentes, mas trazê-las para HTML e CSS simples. Na verdade, é um pequeno passo de seletores de atributos de dados para pseudoseletores personalizados ou seletores baseados em prop ao usar Web Components (pense em <my-card align="left" size="small" />).

Ainda assim, por enquanto, se você ainda não estiver usando elementos personalizados, há uma grande diferença entre

<article
  class="card"
  data-loading="true"
  data-variant="primary"
  data-size="large"
  data-border="top right"
  data-elevation="high" />

e

<button
  aria-label="Toggle menu"
  aria-controls="navbar-menu"
  aria-expanded="true"
  disabled />

Embora eu concorde plenamente com o uso de atributos em vez de classes quando eles já estão presentes, adicionar o seu próprio pode ser uma pílula mais difícil de engolir. Tocar em seletores de atributos nativos não entra em conflito com nenhuma metodologia CSS existente que você possa estar usando, mas adicionar alguns personalizados pode exigir um compromisso maior e alterar as convenções gerais de nomenclatura em sua base de código. Também pode tornar mais difícil avaliar apenas no HTML quais atributos são para comportamento versus apenas questões de estilo. E, por fim, há aquela sensação incômoda de que você está indo contra a corrente ao reaproveitar os atributos de dados para estilizar — afinal, selecionar elementos é para o que servem as classes e para o que os navegadores otimizam no desempenho do seletor.

Mesmo assim, é uma proposta atraente e estou animado para ver como as pessoas continuam a moldá-la. Devon Govett endossa uma divisão em que usa uma classe para o próprio componente, mas sinaliza todas as variantes internas e o estado com data-*ou aria-*atributos. Há alguns grandes insights neste tópico para aqueles que gostariam de se aprofundar.

Postado em Blog
Escreva um comentário