-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from 1Copenut/feature/region-vs-group
[FEATURE] Accordion `role="region"` or `role="group"`
- Loading branch information
Showing
8 changed files
with
701 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
**/.DS_Store | ||
**/node_modules/ | ||
*.*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Accordion roles: region or group? | ||
|
||
This code snippet explores the use of `role="region"` and `role="group"` for accordion (collapsible section) content blocks. [W3C's WAI-ARIA accordion example](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/) uses the region role, but the Mozilla Dev Network (MDN) [accessibility guidance](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/region_role#accessibility_concerns) says to use it sparingly. I wanted to test different markup patterns and see how screen readers interpret information. | ||
|
||
I assume the following markup and conditions: | ||
|
||
1. All collapsible sections are toggled open and closed with a `button[aria-expanded="true | false"]` | ||
2. Content blocks are simple `<div/>` elements with a `hidden` attribute toggled on and off using JavaScript | ||
3. Content blocks receive programmatic focus when they are revealed in the DOM (semantically and visually) | ||
|
||
## Screen reader findings | ||
These findings are not exhaustive. As of October 27, 2023, I have only tested Safari + VoiceOver and Firefox + VoiceOver on MacOS Ventura. Future testing will include Chrome + JAWS and Firefox + NVDA. Mobile testing with iOS devices should also be done. | ||
|
||
### Safari + VoiceOver | ||
|
||
1. `div[role="region"]` Text content received screen reader pointer. Text was read aloud immediately. Container was not named and did not appear in the VO Landmarks menu. | ||
2. `div[role="group"]` Text content received screen reader pointer. Text was read aloud immediately. Container was not named and did not appear in the VO Landmarks menu. | ||
3. `div[role="region"] + aria-labelledby="BUTTON_ID"` Container received screen reader pointer. Container was named and button text appeared in the VO Landmarks menu. Advancing the screen reader pointer read the text aloud. | ||
4. `div[role="group"] + aria-labelledby="BUTTON_ID"` Container received screen reader pointer. Container was not named. Named container did not appear in the VO Landmarks menu. Advancing the screen reader pointer read the text aloud. | ||
|
||
**Conclusion:**<br/> | ||
Example number three, the `div[role="region"] + aria-labelledby="BUTTON_ID"` was the best outcome in my experience. The expanded section was named using the button's accessible name. The expanded content block appeared in the VO Landmarks menu, making it reachable via asynchronous traversal. | ||
|
||
**Minimal HTML markup:** | ||
```html | ||
<div class="cd-accordion--container"> | ||
<button id="button-3" class="cd-button--expand"> | ||
Div role "region" with aria-labelledby | ||
<svg aria-hidden="true" focusable="false" viewBox="0 0 10 10"> | ||
<rect height="8" width="2" y="1" x="4" /> | ||
<rect height="2" width="8" y="4" x="1" /> | ||
</svg> | ||
</button> | ||
<div aria-labelledby="button-3" class="cd-accordion--content" role="region" hidden> | ||
This is the content in group number three. It includes an aria-labelledby attribute. | ||
</div> | ||
</div> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
body { | ||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; | ||
line-height: 1.5; | ||
} | ||
|
||
main { | ||
margin: 0 auto; | ||
width: 72ch; | ||
} | ||
|
||
p { | ||
margin: 0 0 0.5rem; | ||
} | ||
|
||
a { | ||
color: mediumblue; | ||
} | ||
|
||
button { | ||
align-items: center; | ||
background-color: whitesmoke; | ||
border: none; | ||
display: flex; | ||
font-size: 1.25rem; | ||
justify-content: space-between; | ||
line-height: 1.65; | ||
padding: 1rem; | ||
width: 100%; | ||
|
||
& svg { | ||
pointer-events: none; | ||
height: 2rem; | ||
} | ||
} | ||
|
||
button:hover, | ||
button:focus { | ||
background-color: dodgerblue; | ||
} | ||
|
||
button[aria-expanded="true"] rect:first-of-type { | ||
display: none !important; | ||
} | ||
|
||
div.cd-accordion--container { | ||
border: 1px solid dodgerblue; | ||
margin-bottom: 1.25rem; | ||
|
||
& div { | ||
padding: 1rem; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Expandable sections: region or group?</title> | ||
<link rel="stylesheet" href="css/main.css"> | ||
</head> | ||
|
||
<body> | ||
<main> | ||
<h1>Expandable sections: region or group?</h1> | ||
|
||
<div class="cd-accordion--container"> | ||
<button id="button-1" class="cd-button--expand"> | ||
Div role "region" | ||
<svg aria-hidden="true" focusable="false" viewBox="0 0 10 10"> | ||
<rect height="8" width="2" y="1" x="4" /> | ||
<rect height="2" width="8" y="4" x="1" /> | ||
</svg> | ||
</button> | ||
<div role="region" class="cd-accordion--content" hidden> | ||
This is the content in group number one. | ||
</div> | ||
</div> | ||
|
||
<div class="cd-accordion--container"> | ||
<button id="button-2" class="cd-button--expand"> | ||
Div role "group" | ||
<svg aria-hidden="true" focusable="false" viewBox="0 0 10 10"> | ||
<rect height="8" width="2" y="1" x="4" /> | ||
<rect height="2" width="8" y="4" x="1" /> | ||
</svg> | ||
</button> | ||
<div role="group" class="cd-accordion--content" hidden> | ||
This is the content in group number two. | ||
</div> | ||
</div> | ||
|
||
<div class="cd-accordion--container"> | ||
<button id="button-3" class="cd-button--expand"> | ||
Div role "region" with aria-labelledby | ||
<svg aria-hidden="true" focusable="false" viewBox="0 0 10 10"> | ||
<rect height="8" width="2" y="1" x="4" /> | ||
<rect height="2" width="8" y="4" x="1" /> | ||
</svg> | ||
</button> | ||
<div aria-labelledby="button-3" class="cd-accordion--content" role="region" hidden> | ||
This is the content in group number three. It includes an aria-labelledby attribute. | ||
</div> | ||
</div> | ||
|
||
<div class="cd-accordion--container"> | ||
<button id="button-4" class="cd-button--expand"> | ||
Div role "group" with aria-labelledby | ||
<svg aria-hidden="true" focusable="false" viewBox="0 0 10 10"> | ||
<rect height="8" width="2" y="1" x="4" /> | ||
<rect height="2" width="8" y="4" x="1" /> | ||
</svg> | ||
</button> | ||
<div aria-labelledby="button-4" class="cd-accordion--content" role="group" hidden> | ||
This is the content in group number four. It includes an aria-labelledby attribute. | ||
</div> | ||
</div> | ||
</main> | ||
<script src="js/index.js" type="module"></script> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Accordion from "./modules/Accordion.js"; | ||
|
||
const toggles = new Accordion(".cd-button--expand", ".cd-accordion--content"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* I wrote the script, and got help from these fine articles: | ||
* https://inclusive-components.design/collapsible-sections/ | ||
* https://gomakethings.com/detecting-click-events-on-svgs-with-vanilla-js-event-delegation/ | ||
*/ | ||
|
||
/** | ||
* @param {string} buttonElems CSS selector for buttons to trigger accordion. | ||
* @param {string} contentElems CSS selector for content blocks. Typically a DIV or SPAN. | ||
*/ | ||
function Accordion(buttonElems, contentElems) { | ||
const buttons = [...document.querySelectorAll(buttonElems)]; | ||
const contentBlocks = [...document.querySelectorAll(contentElems)]; | ||
|
||
buttons.forEach((button) => { | ||
button.addEventListener("click", (e) => { | ||
const targetButton = e.target; | ||
let targetExpanded = | ||
targetButton.getAttribute("aria-expanded") === "true" || false; | ||
|
||
targetButton.setAttribute("aria-expanded", !targetExpanded); | ||
this.toggleContent(targetButton, targetExpanded); | ||
}); | ||
}); | ||
|
||
contentBlocks.forEach((block) => { | ||
block.addEventListener("blur", (e) => { | ||
e.target.removeAttribute("tabindex"); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* | ||
* @param {HTMLButtonElement} target Button that fired the click event. | ||
* @param {boolean} isContentHidden Is sibling content block hidden? | ||
*/ | ||
Accordion.prototype.toggleContent = function (target, isContentHidden) { | ||
const container = target.nextElementSibling; | ||
container.toggleAttribute("hidden"); | ||
|
||
if (!isContentHidden) { | ||
container.setAttribute("tabindex", "-1"); | ||
container.focus(); | ||
} | ||
}; | ||
|
||
export default Accordion; |
Oops, something went wrong.