O que é o atributo aria-controls e como ele funciona?

O atributo aria-controls é usado para criar uma relação programática entre um elemento que controla a presença de outro elemento na página e o elemento que ele controla. Essa relação pode ajudar usuários de leitores de tela a entender melhor como o controle funciona. Usos comuns incluem um conjunto de abas que controlam a visibilidade de diferentes seções de conteúdo ou um botão que alterna a visibilidade de um menu. Vamos dar uma olhada em um exemplo para ver como isso funciona. Neste exemplo de uma interface com abas, temos um elemento div com um conjunto de três botões:
<link rel="stylesheet" href="styles.css">
<div role="tablist">
  <button role="tab" id="tab1" aria-controls="section1" aria-selected="true">
    Tab 1
  </button>
  <button role="tab" id="tab2" aria-controls="section2" aria-selected="false">
    Tab 2
  </button>
  <button role="tab" id="tab3" aria-controls="section3" aria-selected="false">
    Tab 3
  </button>
</div>
[role="tablist"] {
  display: flex;
  border-bottom: 2px solid #ccc;
  margin-bottom: 1em;
  gap: 4px;
}

[role="tab"] {
  background: #f5f5f5;
  border: 1px solid #ccc;
  border-bottom: none;
  border-radius: 4px 4px 0 0;
  padding: 8px 16px;
  cursor: pointer;
  font-size: 14px;
  color: #333;
  transition: background 0.2s, color 0.2s;
}

[role="tab"]:hover,
[role="tab"]:focus {
  background: #e8f0fe;
  outline: none;
}

[role="tab"][aria-selected="true"] {
  background: #fff;
  border-color: #ccc;
  border-bottom: 2px solid #fff;
  color: #000;
  font-weight: 600;
  position: relative;
  top: 1px; 
}

[role="tab"]:focus-visible {
  outline: 2px solid #0078d4;
  outline-offset: 2px;
}
O div usa role="tablist" para indicar que ele serve como um elemento contêiner para um grupo de abas. Cada botão representa uma aba e possui um atributo role definido como tab. Na maioria das interfaces com abas, o primeiro painel da aba está visível por padrão, então o primeiro botão da aba tem um atributo aria-selected definido como true para indicar que sua seção associada de conteúdo está atualmente visível. O atributo aria-controls é usado para associar cada botão à seção de conteúdo que ele controla. Aqui estão as três seções de conteúdo que correspondem às abas:
<link rel="stylesheet" href="styles.css">
<div role="tablist">
  <button role="tab" id="tab1" aria-controls="section1" aria-selected="true">
    Tab 1
  </button>
  <button role="tab" id="tab2" aria-controls="section2" aria-selected="false">
    Tab 2
  </button>
  <button role="tab" id="tab3" aria-controls="section3" aria-selected="false">
    Tab 3
  </button>
</div>

<div id="section1" role="tabpanel" aria-labelledby="tab1">
  Section 1 content
</div>

<div id="section2" role="tabpanel" aria-labelledby="tab2" hidden>
  Section 2 content
</div>

<div id="section3" role="tabpanel" aria-labelledby="tab3" hidden>
  Section 3 content
</div>
<script src="index.js"></script>
[role="tablist"] {
  display: flex;
  border-bottom: 2px solid #ccc;
  margin-bottom: 1em;
  gap: 4px;
  background: #fafafa;
  padding: 0.25em;
}

[role="tab"] {
  background: #f5f5f5;
  border: 1px solid #ccc;
  border-bottom: none;
  border-radius: 4px 4px 0 0;
  padding: 8px 16px;
  cursor: pointer;
  font-size: 14px;
  color: #333;
  transition: background 0.2s, color 0.2s;
}

[role="tab"]:hover,
[role="tab"]:focus {
  background: #e8f0fe;
  outline: none;
}

[role="tab"][aria-selected="true"] {
  background: #fff;
  border-color: #ccc;
  border-bottom: 2px solid #fff;
  color: #000;
  font-weight: 600;
  position: relative;
  top: 1px;
}

[role="tab"]:focus-visible {
  outline: 2px solid #0078d4;
  outline-offset: 2px;
}

[role="tabpanel"] {
  border: 1px solid #ccc;
  border-radius: 0 4px 4px 4px;
  padding: 16px;
  background-color: #fff;
  color: #333;
  font-size: 15px;
  line-height: 1.4;
}

[role="tabpanel"][hidden] {
  display: none;
}
const tabs = document.querySelectorAll('[role="tab"]');
const tabList = document.querySelector('[role="tablist"]');

tabs.forEach(tab => {
  tab.addEventListener('click', () => {
    activateTab(tab);
  });

  tab.addEventListener('keydown', (e) => {
    const key = e.key;
    const currentIndex = Array.from(tabs).indexOf(tab);
    let newIndex = null;

    if (key === 'ArrowRight') {
      newIndex = (currentIndex + 1) % tabs.length;
    } else if (key === 'ArrowLeft') {
      newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
    } else if (key === 'Home') {
      newIndex = 0;
    } else if (key === 'End') {
      newIndex = tabs.length - 1;
    }

    if (newIndex !== null) {
      tabs[newIndex].focus();
      activateTab(tabs[newIndex]);
    }
  });
});

function activateTab(tab) {
  const tabPanels = document.querySelectorAll('[role="tabpanel"]');

  tabs.forEach(t => {
    t.setAttribute('aria-selected', 'false');
    t.setAttribute('tabindex', '-1');
  });

  tabPanels.forEach(panel => panel.hidden = true);

  tab.setAttribute('aria-selected', 'true');
  tab.removeAttribute('tabindex');

  const panelId = tab.getAttribute('aria-controls');
  const panel = document.getElementById(panelId);
  panel.hidden = false;
  tab.focus();
}
Cada seção de conteúdo possui um atributo role definido como tabpanel e um atributo aria-labelledby que aponta para a aba correspondente para dar ao painel um nome acessível. O atributo hidden é usado para ocultar as seções de conteúdo que não estão atualmente selecionadas. Cada ID de seção corresponde ao valor do atributo aria-controls no botão correspondente. O ID section1 corresponde ao atributo aria-controls no primeiro botão, section2 corresponde ao atributo aria-controls no segundo botão e section3 corresponde ao atributo aria-controls no terceiro botão. É assim que a associação entre as abas e suas seções de conteúdo é estabelecida. Para alternar entre as diferentes seções você precisará usar JavaScript para atualizar o atributo hidden na seção e o atributo aria-selected no botão com base em qual seção está atualmente visível. Apenas uma seção pode estar visível por vez e ela não deve ter o atributo hidden e aria-selected deve estar definido como true no seu botão. As seções ocultas restantes devem todas ter o atributo hidden e aria-selected deve ser definido como false em seus botões. Abas são um caso comum de uso para o atributo aria-controls, mas ele pode ser usado em outros cenários onde um elemento controla a visibilidade ou o comportamento de outro elemento. Outros exemplos incluem accordions, dropdown menus e modals. Na próxima vez que você projetar uma interface de usuário que envolva controlar a visibilidade dos elementos, considere usar o atributo aria-controls para estabelecer a relação entre o elemento controlador e o elemento controlado.
Este módulo não possui perguntas. Marque como concluído.