curriculum/challenges/english/blocks/lecture-understanding-aria-expanded-aria-live-and-common-aria-states/672a5507d857a891139abc7f.md
The aria-controls attribute is used to create a programmatic relationship between an element that controls the presence of another element on the page, and the element it controls. This relationship can help screen reader users better understand how the control works. Common uses include a set of tabs that control the visibility of different sections of content, or a button that toggles the visibility of a menu.
Let's take a look at an example to see how this works. In this example of a tabbed interface, we have a div element with a set of three buttons:
:::interactive_editor
<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;
}
:::
The div uses role="tablist" to indicate that it serves as a container element for a group of tabs.
Each button represents a tab and has a role attribute set to tab. In most tabbed interfaces, the first tab panel is visible by default, so the first tab button has an aria-selected attribute set to true to indicate that its associated section of content is currently visible. The aria-controls attribute is used to associate each button with the section of content that it controls.
Here are the three sections of content that correspond to the tabs:
:::interactive_editor
<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();
}
:::
Each section of content has a role attribute set to tabpanel and an aria-labelledby attribute that points to the corresponding tab to give the panel an accessible name. The hidden attribute is used to hide the sections of content that are not currently selected. Each section ID matches the value of the aria-controls attribute on the corresponding button. The section1 ID matches the aria-controls attribute on the first button, section2 matches the aria-controls attribute on the second button, and section3 matches the aria-controls attribute on the third button. This is how the association between the tabs and their sections of content is established.
To switch between the different sections you will need to use JavaScript to update the hidden attribute on the section and the aria-selected attribute on the button based on which section is currently visible. Only one section can be visible at a time and it should not have the hidden attribute and aria-selected should be set to true on its button. The remaining hidden sections should all have the hidden attribute and aria-selected should be set to false on their buttons.
Tabs are a common use case for the aria-controls attribute, but it can be used in other scenarios where one element controls the visibility or behavior of another element. Other examples include accordions, dropdown menus, and modals.
The next time you design a user interface that involves controlling the visibility of elements, consider using the aria-controls attribute to establish the relationship between the controlling element and the controlled element.
What is the purpose of the aria-controls attribute in the context of tabs?
To specify which tab is currently active.
Think about the association between tabs and content sections.
To hide or show sections of content.
Think about the association between tabs and content sections.
To associate a tab with the section it controls.
To set the role of the element.
Think about the association between tabs and content sections.
3
How does the aria-controls attribute affect the content sections in the example?
It automatically updates the content based on user input.
Think about how the attribute is used to control the visibility of content sections.
It hides or shows sections based on which tab is selected.
It changes the tab's style to indicate it is selected.
Think about how the attribute is used to control the visibility of content sections.
It defines the visual appearance of the tab.
Think about how the attribute is used to control the visibility of content sections.
2
In the provided example, how is the aria-labelledby attribute used?
To specify which tab controls which content section.
It links the tab to the content section it controls.
To identify the content section that a tab controls.
To define the role of the tab and content sections.
It links the tab to the content section it controls.
To automatically switch between content sections when a tab is clicked.
It links the tab to the content section it controls.
2