├── .gitignore ├── .npmignore ├── package.json ├── LICENSE ├── CHANGELOG.md ├── assets ├── js │ └── aria.accordion.min.js └── css │ └── aria.accordion.css ├── README.md ├── index.html └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | index.html 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Accessible ARIA Accordions", 3 | "name": "a11y_accordions", 4 | "description": "ES5 script to create accessible accordion interfaces", 5 | "version": "3.2.1", 6 | "main": "index.js", 7 | "homepage": "https://github.com/scottaohara/a11y_accordions#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://scottaohara@github.com/scottaohara/a11y_accordions.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/scottaohara/a11y_accordions/issues" 14 | }, 15 | "keywords": [ 16 | "a11y", 17 | "aria", 18 | "accessibility", 19 | "accordion", 20 | "javascript", 21 | "es5" 22 | ], 23 | "license": "MIT", 24 | "author": [ 25 | { 26 | "name": "Scott O'Hara", 27 | "email": "sao.npm@gmail.com", 28 | "web": "https://www.scottohara.me" 29 | } 30 | ], 31 | "scripts": { 32 | "test": "echo \"Error: no test specified\" && exit 1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased] 5 | - Optional feature to add support for up/down arrow keys. 6 | - Allow for custom component classes to be declared via a data-class attribute. 7 | 8 | ## [3.2.1] - 2018-06-28 9 | ### Changed 10 | - Fixed instructions for CSS selectors in readme file. 11 | 12 | 13 | ## [3.2.0] - 2018-06-28 14 | ### Added 15 | - package.json and registered as NPM package. 16 | - CHANGELOG.md 17 | 18 | ### Changed 19 | - `data-transition` attribute no longer allows for a value. Instead it merely adds a transition class to each accordion panel to allow for transitions for specific CSS properties to be customized in the CSS file. 20 | - Instead of requiring `.js` prior to CSS selectors, remove this `.js` selector dependency and have the base markup utilize data attributes. When the script runs on page load, the classes that were there by default will now be applied by the script. 21 | 22 | ## [3.1.1] - 2018-06-11 23 | ### Added 24 | - Add "arrows" for accordion trigger styling. 25 | 26 | 27 | ## [3.1.0] - 2018-02-09 28 | ### Changed 29 | - [Modify the manner in which IDs are generated](https://github.com/scottaohara/a11y_accordions/commit/5723fafddac2dcbf102a0f99bd9f6d3b2e676dd1). 30 | 31 | 32 | ## [3.0.0] - 2018-02-09 33 | ### Added 34 | - CSS to ensure list semantics are respected with Safari + VoiceOver. 35 | 36 | ### Changed 37 | - Updated documentation and notes about screen reader usability. 38 | - Allow accordions to have an `ol` or `ul` base markup pattern. 39 | - Fix bug with multi-accordion panels. 40 | 41 | 42 | ## [2.0.1] - 2017-10-27 43 | ### Changed 44 | - NaN bug fix. 45 | 46 | 47 | ## [2.0.0] - 2017-10-24 48 | ### Changed 49 | - Converted script from jQuery to ES5 Vanilla JavaScript. 50 | 51 | ### Removed 52 | - jQuery dependency 53 | - CSS :target selector for no-js functionality. Instead the no-js functionality should just be the static underlying markup. 54 | 55 | 56 | ## 1.0.2 - 2017-10-24 57 | ### Added 58 | - CSS :target selector for no-js functionality 59 | 60 | ### Changed 61 | - Fix spacebar bug with Firefox 62 | - Modified styling selectors 63 | - Smarter selectors for nesting of accordion widgets 64 | - Timeout function to mitigate Firefox animation issue 65 | -------------------------------------------------------------------------------- /assets/js/aria.accordion.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e,a){"use strict";var r={};t.ARIAaccordion=r,r.NS="ARIAaccordion",r.AUTHOR="Scott O'Hara",r.VERSION="3.2.1",r.LICENSE="https://github.com/scottaohara/accessible_accordions/blob/master/LICENSE";var i="accordion",l=i+"__trigger",n=i+"__panel",o="[data-aria-accordion-heading]",d="[data-aria-accordion-panel]",c=0;r.create=function(){var t,a,s,u,A,g,h="none",b=e.querySelectorAll("[data-aria-accordion]");for(c+=1,g=0;g li").length?(a=e.querySelectorAll("#"+t.id+" li > "+d),s=e.querySelectorAll("#"+t.id+" li > "+o)):(a=e.querySelectorAll("#"+t.id+" > "+d),s=e.querySelectorAll("#"+t.id+" > "+o)),t.hasAttribute("data-default")&&(h=t.getAttribute("data-default")),A=t.hasAttribute("data-constant"),t.hasAttribute("data-multi"),t.hasAttribute("data-transition")){var y=t.querySelectorAll(d);for(f=0;f li").length?e.querySelectorAll("#"+t.id+" li > "+o+" ."+l):e.querySelectorAll("#"+t.id+" > "+o+" ."+l),f=0;f=e.length?s(e[e.length-1],!1):s(e[d-1],!1)),(c&&"none"===d||NaN===parseInt(d))&&s(e[0],!1)},r.setupHeadingButton=function(t,a){var r,i,n,o,d,c;for(c=0;c li").length?e.querySelectorAll("#"+i+" li > "+o+" ."+l):e.querySelectorAll("#"+i+" > "+o+" ."+l),t.preventDefault(),r.togglePanel(t,i,n,a)},r.togglePanel=function(t,a,r,i){var l,n,o=t.target;if("true"!==o.getAttribute("aria-disabled")&&(l=o.getAttribute("aria-controls"),g(o,"true"),"true"===o.getAttribute("aria-expanded")?(u(o,"false"),s(r,"true")):(u(o,"true"),s(r,"false"),e.getElementById(a).hasAttribute("data-constant")&&A(o,"true")),e.getElementById(a).hasAttribute("data-constant")||!e.getElementById(a).hasAttribute("data-multi")))for(n=0;n li").length?e.querySelectorAll("#"+i+" li > "+o+" ."+l):e.querySelectorAll("#"+i+" > "+o+" ."+l),r){case 35:t.preventDefault(),a[a.length-1].focus();break;case 36:t.preventDefault(),a[0].focus()}}},r.init=function(){r.create()};var s=function(t,e){t.setAttribute("aria-hidden",e)},u=function(t,e){t.setAttribute("aria-expanded",e)},A=function(t,e){t.setAttribute("aria-disabled",e)},g=function(t,e){t.setAttribute("data-current",e)};r.init()}(window,document); 2 | -------------------------------------------------------------------------------- /assets/css/aria.accordion.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | } 6 | 7 | /** 8 | * Accordion container element 9 | */ 10 | .accordion { 11 | list-style: none; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | .accordion > li { 17 | margin: 0; 18 | } 19 | 20 | /** 21 | * Add zero-width space. needed to ensure Safari + VO respect list semantics. 22 | * Set the before content to position absolute to negate any visible space 23 | * the before content could add to the document. 24 | */ 25 | .accordion > li:before { 26 | content: "\200B"; 27 | position: absolute; 28 | } 29 | 30 | /** 31 | * Accordion Heading 32 | */ 33 | .accordion__heading { 34 | border: 1px solid #4464c2; 35 | font-size: inherit; 36 | margin: -1px 0 0; 37 | } 38 | 39 | .accordion__trigger { 40 | -webkit-appearance: none; 41 | background-color: #fafafa; 42 | border: none; 43 | border-radius: 0; 44 | box-shadow: none; 45 | color: #4464c2; 46 | cursor: pointer; 47 | display: block; 48 | font-size: inherit; 49 | margin: 0; 50 | padding: .5em 2em .5em 1em; 51 | position: relative; 52 | text-align: left; 53 | width: 100%; 54 | z-index: 2; 55 | } 56 | 57 | .accordion__trigger:after { 58 | border-left: .4em solid transparent; 59 | border-right: .4em solid transparent; 60 | border-top: .5em solid #222; 61 | bottom: 0; 62 | content: ''; 63 | height: 0; 64 | margin: auto; 65 | position: absolute; 66 | right: 1em; 67 | top: 0; 68 | transition: transform .2s ease-in-out; 69 | transform-origin: center center; 70 | transform: rotate(0deg); 71 | width: 0; 72 | } 73 | 74 | .accordion__trigger:hover:after, 75 | .accordion__trigger:focus:after, 76 | .accordion__trigger[aria-expanded="true"]:after { 77 | border-top-color: #fff; 78 | } 79 | 80 | .accordion__trigger[aria-expanded="true"]:after { 81 | transform: rotate(180deg); 82 | } 83 | 84 | /** 85 | * This is needed to allow a double tap iOS 11 86 | * Safari + VO to function correctly, if there 87 | * are multiple elements (wrapper spans) to layout 88 | * text nodes within the accordion button. 89 | 90 | -- This is not needed if a button only contains text 91 | and no other child nodes wrapping the text -- 92 | 93 | .accordion__trigger > * { 94 | pointer-events: none; 95 | } 96 | */ 97 | 98 | .accordion__trigger:hover, 99 | .accordion__trigger:focus { 100 | background-color: #0e3b5e; 101 | color: #fff; 102 | outline: none; 103 | } 104 | 105 | .accordion__trigger:focus { 106 | box-shadow: inset 0 0 0 2px #1e82d1; 107 | } 108 | 109 | .accordion__trigger[aria-disabled="true"]:hover { 110 | background-color: #1b75bc; 111 | color: #fff; 112 | cursor: not-allowed; 113 | } 114 | 115 | .accordion__trigger[aria-disabled="true"]:focus { 116 | background-color: #0a2a42; 117 | } 118 | 119 | .accordion__panel { 120 | background-color: inherit; 121 | max-height: 0vh; 122 | overflow: hidden; 123 | padding: 0.001em 1.25em; 124 | position: relative; 125 | visibility: hidden; 126 | z-index: 1; 127 | } 128 | 129 | .accordion__panel--transition { 130 | transition: 131 | max-height .2s ease-in-out, 132 | padding-top .2s ease-in-out, 133 | padding-bottom .2s ease-in-out; 134 | } 135 | 136 | .accordion__panel > :last-child { 137 | margin-bottom: 0; 138 | } 139 | 140 | .accordion__panel[aria-hidden="false"] { 141 | max-height: 100vh; 142 | overflow: auto; 143 | padding: 1.25em; 144 | visibility: visible; 145 | } 146 | 147 | .accordion__trigger[aria-expanded="true"] { 148 | background: #1b75bc; 149 | color: #fff; 150 | } 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Accessible accordions & disclosure widgets 2 | At its essence, an accordion component consists of a series of related [disclosure widgets](http://w3c.github.io/aria-practices/#disclosure) (aka toggle buttons that show/hide their related content). These accordion widgets are visually related to each other, and are grouped siblings in the DOM (as either a series of content sections within a wrapper element, or within a list element). Often accordions are programmatically aware of their siblings' current state, though this is optional behavior. 3 | 4 | * UX based on [ARIA Authoring Practices](https://w3c.github.io/aria-practices/#accordion) 5 | * [Accessible accordions demo page](https://scottaohara.github.io/a11y_accordions/) 6 | * [Accessible accordions blog article](https://www.scottohara.me/blog/2017/10/25/accordion-release.html) 7 | 8 | ## Install 9 | You can get this package [on npm](https://www.npmjs.com/package/a11y_accordions): 10 | ``` 11 | $ npm i a11y_accordions 12 | ``` 13 | 14 | Clone it: 15 | ``` 16 | $ git clone https://github.com/scottaohara/a11y_accordions.git 17 | ``` 18 | 19 | Or [download a zip of the repository](https://github.com/scottaohara/a11y_accordions/archive/master.zip). 20 | 21 | The CSS for this component is included in `assets/css/`. The classes are added to the accordion markup when the script/page loads. Presently, changing the class names requires a change in the JavaScript file and the CSS. Adjust the styles as necessary for your project. 22 | 23 | 24 | ## Minimum Required Mark-up 25 | ```html 26 |
27 |

28 | Heading Here 29 |

30 |
31 |

32 | Content here 33 |

34 |
35 | 38 |
39 | ``` 40 | 41 | ### Under the hood 42 | The `data-aria-accordion` attribute is the key for initiating the process to convert the minimum markup into a functioning accordion component. 43 | 44 | If an `id` is not pre-set on the accordion wrapper, then one will be auto generated. An ID is necessary to serve as the basis for populating generated IDs onto the child accordion panels. More on why these IDs are needed, later. 45 | 46 | When an accordion is identified, the setup script continues to run and identify each heading and panel within the accordion. The different `data-aria-accordion-` attributes are necessary for the setup process to run and appropriately identify the key elements of the accordion. 47 | 48 | ### During the setup process the following occurs: 49 | * The classes for an accordion, and its child headings and panels will be added, corresponding to the appropriate `data-` attributes. 50 | * The panels are hidden, and if a default panel was set (see options) an attribute of `aria-hidden="false"` will be set to that panel. 51 | * The `id` of the accordion container is used as the basis to generate unique IDs for each of the panels of the accordion. 52 | * a `