Como codificar um componente acordeão JavaScript acessível

Neste tutorial, criaremos um componente de bloco sanfona acessível usando JavaScript vanilla, que permite ao usuário alternar o conteúdo recolhível.

1. Criando o layout (HTML)

Como estamos construindo um componente personalizado, nosso foco será torná-lo acessível para que possa ser operado por todos os usuários.

Este é o HTML para o nosso layout acordeão:

1
   id="accordion" class="accordion">
2
     class="accordion-container">
3
       class="accordion-item">
4
         class="accordion-trigger" id="accordion-trigger-1" aria-expanded="false" aria-controls="accordion-content-1">
5
           class="accordion-title">
6
          
7
           class="accordion-icon">
8
            +
9
          
10
        
11
         class="accordion-content" id="accordion-content-1" role="region" aria-labelledby="accordion-trigger-1">
12
          

13
        
14
      
15
      
16
      ...
17
    
18
  

Tornar as coisas acessíveis

Vamos dar uma olhada nos atributos que usamos para auxiliar na acessibilidade:

  • aria-expanded: este atributo é usado em elementos recolhíveis para indicar se o elemento é expandido ou não.
  • aria-controls: este atributo é usado em um elemento que é responsável por exibir o conteúdo de outro elemento. o aria-controls value é o id do elemento que está sendo controlado.
  • aria-labelledby: fornece um nome acessível a um elemento.
  • role: este atributo é usado para atribuir um significado semântico a um elemento. Para este tutorial, estamos atribuindo nosso conteúdo sanfonado à região de função.
Árvore de acessibilidade do Devtools do componente sanfona mostrando os valores aria-labelledby e roleÁrvore de acessibilidade do Devtools do componente sanfona mostrando os valores aria-labelledby e roleÁrvore de acessibilidade do Devtools do componente sanfona mostrando os valores aria-labelledby e role
Árvore de acessibilidade do Devtools do componente sanfona mostrando os valores aria-labelledby e role

2. Estilizando o conteúdo (CSS)

Usaremos CSS para lidar com o tempo de transição dos elementos que queremos animar. Nesse caso, esse é o accordion-content e accordion-icon. Também definiremos o estilo para quando o accordion-item está ativo e oculta o conteúdo do acordeon por padrão.

1
.accordion-icon {
2
  transition: transform 0.5s;
3
}
4
5
.accordion-item.is-active .accordion-icon {
6
  transform: rotate(45deg);
7
}
8
9
.accordion-content {
10
  height: 0;
11
  overflow: hidden;
12
  transition: 0.5s;
13
}

Este é o resto do estilo para o layout:

1
.accordion-container {
2
  width: 90%;
3
  max-width: 1240px;
4
  margin: 0 auto;
5
  border: 3px solid #e0e0e0;
6
  border-radius: 24px;
7
  overflow: hidden;
8
}
9
10
.accordion-item {
11
  width: 100%;
12
}
13
14
.accordion-trigger {
15
  width: 100%;
16
  display: block;
17
  background-color: rgb(250, 250, 250);
18
  color: rgb(0, 0, 0);
19
  padding: 24px;
20
  font-size: 20px;
21
  font-weight: 500;
22
  font-family: 'Inter', sans-serif;
23
  text-align: left;
24
  border: none;
25
  display: flex;
26
  gap: 16px;
27
  justify-content: space-between;
28
  cursor: pointer;
29
}
30
31
.accordion-item:not(:first-of-type) .accordion-trigger {
32
  border-top: 3px solid #eaeaea;
33
}
34
35
.accordion-content {
36
  height: 0;
37
  overflow: hidden;
38
  transition: 0.5s;
39
}
40
41
.accordion-content p {
42
  margin: 24px;
43
}

Uma coisa a observar é que não usamos preenchimento ou margens em nosso elemento acordeon-content e, em vez disso, apenas aplicamos uma margem ao elemento filho. Isto é porque height:0 não afeta as margens ou o preenchimento de um elemento, então ainda teríamos espaço visível se definissemos algum.

3. Manipulando a Funcionalidade do Acordeão (JavaScript)

Primeiro, precisamos obter todos os itens do acordeão:

1
const accordionItems = document.querySelectorAll(".accordion-item");

Ouvinte de evento de clique

Em seguida, definiremos uma função no carregamento da página para adicionar um ouvinte de evento de clique a todos os botões de acionamento sanfonados.

1
window.addEventListener("load", () => {
2
  accordionItems.forEach((accordion, index) => {
3
    const accordionTrigger = accordion.querySelector(".accordion-trigger");
4
    accordionTrigger.addEventListener("click", () => toggleAccordion(index));
5
  });
6
});

Uma vez que nossa .querySelectorAll() método retorna um NodeList, podemos usar o .forEach() para percorrer cada elemento de item sanfonado.

Podemos então direcionar cada botão de gatilho dentro de um item de acordeão específico usando accordion.querySelector(). Construir o componente dessa forma torna-o mais escalável, pois o direcionamento do gatilho ou elemento de conteúdo não está vinculado a um ID ou nome de classe específico e, em vez disso, é baseado no contêiner de item sanfonado em que está presente.

Nossa função de alternância

Agora que configuramos nosso ouvinte de evento, podemos definir nosso toggleAccordion() função.

Primeiro, queremos segmentar o item acordeão atual. É possível fazer isso usando e.target.parentNode mas, para esta demonstração, usaremos o índice, que o toggleFunction aceita como parâmetro.

1
const toggleAccordion = (index) => {
2
  const currentAccordion = accordionItems[index];
3
  currentAccordion.classList.toggle("is-active");
4
};

Quando o gatilho sanfona for clicado, alternaremos a classe is-active no item sanfona correspondente.

Essa função também manipulará a atualização da altura do conteúdo do acordeão e definirá o valor expandido da ária do acionador do acordeão como falso.

Estamos definindo a altura em JavaScript para criar um efeito de transição no conteúdo do acordeão, pois não há como saber a altura exata do conteúdo do acordeão em CSS.

Isto é o que o nosso atualizado toggleAccordion() função se parece com:

1
const toggleAccordion = (index) => {
2
  resetAccordions(index);
3
4
  const currentAccordion = accordionItems[index];
5
  currentAccordion.classList.toggle("is-active");
6
7
  const accordionContent = currentAccordion.querySelector(".accordion-content");
8
  const accordionTrigger = currentAccordion.querySelector(".accordion-trigger");
9
10
  if (currentAccordion.classList.contains("is-active")) {
11
    accordionContent.style.height = `${accordionContent.scrollHeight}px`;
12
    accordionTrigger.setAttribute("aria-expanded", "true");
13
  } else {
14
    accordionContent.style.height = 0;
15
    accordionTrigger.setAttribute("aria-expanded", "false");
16
  }
17
};

Depois de alternar a classe is-active no currentAccordion, usaremos a presença da classe is-active para determinar os atributos da ária e o estilo do gatilho e do conteúdo do acordeon.

Incluindo uma função de reinicialização

Podemos expandir ainda mais essa compilação incluindo uma função de redefinição para garantir que apenas um acordeão seja aberto por vez. Nossa função de redefinição fará um loop através do accordionItems NodeList e remova o estado ativo em cada item sanfonado, exceto o atual com base em seu índice.

Vamos definir o resetAccordions() função:

1
const resetAccordions = (targetIndex) => {
2
  accordionItems.forEach((accordion, index) => {
3
    const accordionContent = accordion.querySelector(".accordion-content");
4
    const accordionTrigger = accordion.querySelector(".accordion-trigger");
5
6
    if (targetIndex != index) {
7
      accordion.classList.remove("is-active");
8
      accordionContent.style.height = 0;
9
      accordionTrigger.setAttribute("aria-expanded", "false");
10
    }
11
  });
12
};

Nós fazemos um loop através do accordionItem e segmentar todos os elementos em que o índice do item não é igual ao targetIndex sendo passado para o resetAccordions() função.

Finalmente, podemos atualizar nosso toggleAccordion() função para incluir a redefinição do bloco sanfona quando um item sanfonado é clicado:

1
const toggleAccordion = (index) => {
2
  resetAccordions(index);
3
4
  const currentAccordion = accordionItems[index];
5
  currentAccordion.classList.toggle("is-active");
6
7
  const accordionContent = currentAccordion.querySelector(".accordion-content");
8
  const accordionTrigger = currentAccordion.querySelector(".accordion-trigger");
9
10
  if (currentAccordion.classList.contains("is-active")) {
11
    accordionContent.style.height = `${accordionContent.scrollHeight}px`;
12
    accordionTrigger.setAttribute("aria-expanded", "true");
13
  } else {
14
    accordionContent.style.height = 0;
15
    accordionTrigger.setAttribute("aria-expanded", "false");
16
  }
17
};

Conclusão

E com isso, construímos completamente um componente sanfona JavaScript! Bom trabalho. Vamos nos lembrar do produto acabado:

Deixe uma resposta