Skip to content

Commit b2d9092

Browse files
chrisdhanarajmarijohannessen
authored andcommitted
fix(a11y): accordion a11y fixes (#262)
* fix(a11y): accordion a11y fixes * fix(a11y): fix accordion tests * fix(mixin): include font props on mixin
1 parent f3d4238 commit b2d9092

File tree

8 files changed

+232
-71
lines changed

8 files changed

+232
-71
lines changed

gulpfile.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,12 @@ gulp.task('scripts:compiled', ['scripts:rollup'], cb => {
136136
const srcFile = './scripts/carbon-components.js';
137137

138138
pump(
139-
[gulp.src(srcFile), uglify(), rename('carbon-components.min.js'), gulp.dest('scripts')],
139+
[
140+
gulp.src(srcFile),
141+
uglify(),
142+
rename('carbon-components.min.js'),
143+
gulp.dest('scripts'),
144+
],
140145
cb
141146
);
142147
});
@@ -221,7 +226,13 @@ gulp.task('html:source', () => {
221226

222227
gulp.task('lint', () =>
223228
gulp
224-
.src(['gulpfile.js', 'server.js', 'src/**/*.js', 'tests/**/*.js', 'demo/**/*.js'])
229+
.src([
230+
'gulpfile.js',
231+
'server.js',
232+
'src/**/*.js',
233+
'tests/**/*.js',
234+
'demo/**/*.js',
235+
])
225236
.pipe(eslint())
226237
.pipe(eslint.format())
227238
.pipe(eslint.failAfterError())
@@ -305,13 +316,16 @@ gulp.task('test:a11y', ['sass:compiled'], done => {
305316
showOnlyViolations: true,
306317
exclude: '.offleft, #flex-col, #flex-row',
307318
tags: ['wcag2aa', 'wcag2a'],
308-
folderOutputReport: componentName === undefined ? 'tests/axe/allHtml' : 'tests/axe',
309-
saveOutputIn: componentName === undefined
310-
? `a11y-html.json`
311-
: `a11y-${componentName}.json`,
312-
urls: componentName === undefined
313-
? ['http://localhost:3000']
314-
: [`http://localhost:3000/components/${componentName}/`],
319+
folderOutputReport:
320+
componentName === undefined ? 'tests/axe/allHtml' : 'tests/axe',
321+
saveOutputIn:
322+
componentName === undefined
323+
? `a11y-html.json`
324+
: `a11y-${componentName}.json`,
325+
urls:
326+
componentName === undefined
327+
? ['http://localhost:3000']
328+
: [`http://localhost:3000/components/${componentName}/`],
315329
};
316330

317331
return axe(options, done);

src/components/accordion/_accordion.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,26 @@
3434
}
3535

3636
.bx--accordion__heading {
37+
@include button-reset;
38+
3739
display: flex;
3840
align-items: center;
3941
justify-content: $accordion-justify-content;
4042
flex-direction: $accordion-flex-direction;
4143
cursor: pointer;
4244
padding: .5rem 0;
45+
46+
// new version of markup uses focus on the heading,
47+
// not the list element itself
48+
&:focus {
49+
outline: none;
50+
51+
.bx--accordion__arrow {
52+
@include focus-outline('border');
53+
overflow: visible; // safari fix
54+
outline-offset: -.5px; // safari fix
55+
}
56+
}
4357
}
4458

4559
.bx--accordion__arrow {
Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,50 @@
11
<ul data-accordion class="bx--accordion">
2-
<li tabindex="0" data-accordion-item class="bx--accordion__item">
3-
<div class="bx--accordion__heading">
2+
<li data-accordion-item class="bx--accordion__item">
3+
<button class="bx--accordion__heading" aria-expanded="false" aria-controls="pane1">
44
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
55
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
66
</svg>
77
<p class="bx--accordion__title">Label</p>
8-
</div>
9-
<div class="bx--accordion__content">
8+
</button>
9+
<div id="pane1" class="bx--accordion__content">
1010
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
1111
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
1212
</div>
1313
</li>
14-
<li tabindex="0" data-accordion-item class="bx--accordion__item">
15-
<div class="bx--accordion__heading">
14+
<li data-accordion-item class="bx--accordion__item">
15+
<button class="bx--accordion__heading" aria-expanded="false" aria-controls="pane2">
1616
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
1717
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
1818
</svg>
1919
<p class="bx--accordion__title">Label with multiple words</p>
20-
</div>
21-
<div class="bx--accordion__content">
20+
</button>
21+
<div id="pane2" class="bx--accordion__content">
2222
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
2323
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
2424
</div>
2525
</li>
26-
<li tabindex="0" data-accordion-item class="bx--accordion__item">
27-
<div class="bx--accordion__heading">
26+
<li data-accordion-item class="bx--accordion__item">
27+
<button class="bx--accordion__heading" aria-expanded="false" aria-controls="pane3">
2828
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
2929
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
3030
</svg>
3131
<p class="bx--accordion__title">Label</p>
32-
</div>
33-
<div class="bx--accordion__content">
32+
</button>
33+
<div id="pane3" class="bx--accordion__content">
3434
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
3535
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
3636
</div>
3737
</li>
38-
<li tabindex="0" data-accordion-item class="bx--accordion__item">
39-
<div class="bx--accordion__heading">
38+
<li data-accordion-item class="bx--accordion__item">
39+
<button class="bx--accordion__heading" aria-expanded="false" aria-controls="pane4">
4040
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
4141
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
4242
</svg>
4343
<p class="bx--accordion__title">Label</p>
44-
</div>
45-
<div class="bx--accordion__content">
44+
</button>
45+
<div id="pane4" class="bx--accordion__content">
4646
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
4747
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
4848
</div>
4949
</li>
50-
</ul>
50+
</ul>

src/components/accordion/accordion.js

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import mixin from '../../globals/js/misc/mixin';
22
import createComponent from '../../globals/js/mixins/create-component';
3-
import initComponentBySearch
4-
from '../../globals/js/mixins/init-component-by-search';
3+
import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
54
import eventMatches from '../../globals/js/misc/event-matches';
65

76
class Accordion extends mixin(createComponent, initComponentBySearch) {
@@ -13,19 +12,41 @@ class Accordion extends mixin(createComponent, initComponentBySearch) {
1312
*/
1413
constructor(element, options) {
1514
super(element, options);
16-
this.element.addEventListener('click', (event) => {
15+
this.element.addEventListener('click', event => {
1716
const item = eventMatches(event, this.options.selectorAccordionItem);
1817
if (item && !eventMatches(event, this.options.selectorAccordionContent)) {
19-
item.classList.toggle(this.options.classActive);
18+
this._toggle(item);
2019
}
2120
});
2221

23-
this.element.addEventListener('keypress', (event) => {
24-
const item = eventMatches(event, this.options.selectorAccordionItem);
25-
if (item && !eventMatches(event, this.options.selectorAccordionContent)) {
26-
this._handleKeypress(event);
27-
}
28-
});
22+
/**
23+
*
24+
* DEPRECATE in v8
25+
*
26+
* Swapping to a button elemenet instead of a div
27+
* automatically maps click events to keypress as well
28+
* This event listener now is only added if user is using
29+
* the older markup
30+
*/
31+
32+
if (!this._checkIfButton()) {
33+
this.element.addEventListener('keypress', event => {
34+
const item = eventMatches(event, this.options.selectorAccordionItem);
35+
36+
if (
37+
item &&
38+
!eventMatches(event, this.options.selectorAccordionContent)
39+
) {
40+
this._handleKeypress(event);
41+
}
42+
});
43+
}
44+
}
45+
46+
_checkIfButton() {
47+
return (
48+
this.element.firstElementChild.firstElementChild.nodeName === 'BUTTON'
49+
);
2950
}
3051

3152
/**
@@ -34,10 +55,26 @@ class Accordion extends mixin(createComponent, initComponentBySearch) {
3455
*/
3556
_handleKeypress(event) {
3657
if (event.which === 13 || event.which === 32) {
37-
event.target.classList.toggle(this.options.classActive);
58+
this._toggle(event.target);
3859
}
3960
}
4061

62+
_toggle(element) {
63+
const heading = element.querySelector(
64+
this.options.selectorAccordionItemHeading
65+
);
66+
const expanded = heading.getAttribute('aria-expanded');
67+
68+
if (expanded !== null) {
69+
heading.setAttribute(
70+
'aria-expanded',
71+
expanded === 'true' ? 'false' : 'true'
72+
);
73+
}
74+
75+
element.classList.toggle(this.options.classActive);
76+
}
77+
4178
/**
4279
* The component options.
4380
* If `options` is specified in the constructor,
@@ -48,6 +85,7 @@ class Accordion extends mixin(createComponent, initComponentBySearch) {
4885
static options = {
4986
selectorInit: '[data-accordion]',
5087
selectorAccordionItem: '.bx--accordion__item',
88+
selectorAccordionItemHeading: '.bx--accordion__heading',
5189
selectorAccordionContent: '.bx--accordion__content',
5290
classActive: 'bx--accordion__item--active',
5391
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<ul data-accordion class="bx--accordion">
2+
<li tabindex="0" data-accordion-item class="bx--accordion__item">
3+
<div class="bx--accordion__heading">
4+
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
5+
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
6+
</svg>
7+
<p class="bx--accordion__title">Label</p>
8+
</div>
9+
<div class="bx--accordion__content">
10+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
11+
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
12+
</div>
13+
</li>
14+
<li tabindex="0" data-accordion-item class="bx--accordion__item">
15+
<div class="bx--accordion__heading">
16+
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
17+
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
18+
</svg>
19+
<p class="bx--accordion__title">Label with multiple words</p>
20+
</div>
21+
<div class="bx--accordion__content">
22+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
23+
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
24+
</div>
25+
</li>
26+
<li tabindex="0" data-accordion-item class="bx--accordion__item">
27+
<div class="bx--accordion__heading">
28+
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
29+
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
30+
</svg>
31+
<p class="bx--accordion__title">Label</p>
32+
</div>
33+
<div class="bx--accordion__content">
34+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
35+
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
36+
</div>
37+
</li>
38+
<li tabindex="0" data-accordion-item class="bx--accordion__item">
39+
<div class="bx--accordion__heading">
40+
<svg class="bx--accordion__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
41+
<path d="M0 10.6L4.7 6 0 1.4 1.4 0l6.1 6-6.1 6z"></path>
42+
</svg>
43+
<p class="bx--accordion__title">Label</p>
44+
</div>
45+
<div class="bx--accordion__content">
46+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
47+
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
48+
</div>
49+
</li>
50+
</ul>

src/globals/scss/_css--helpers.scss

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
@mixin css-helpers {
66
.bx--visually-hidden {
7-
position:absolute;
8-
width:1px;
9-
height:1px;
10-
padding:0;
11-
margin:-1px;
12-
overflow:hidden;
13-
clip:rect(0, 0, 0, 0);
14-
border:0;
7+
position: absolute;
8+
width: 1px;
9+
height: 1px;
10+
padding: 0;
11+
margin: -1px;
12+
overflow: hidden;
13+
clip: rect(0, 0, 0, 0);
14+
border: 0;
1515
visibility: visible;
1616
white-space: nowrap;
1717
}

src/globals/scss/_helper-mixins.scss

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
// Deprecated || Not used anymore
99
// ===========================================
1010

11-
1211
//----------------------------------------------
1312
// Misc
1413
// ---------------------------------------------
1514

15+
@import 'css--reset';
16+
@import 'typography';
17+
1618
@mixin text-overflow($width) {
1719
display: block;
1820
width: $width;
@@ -66,6 +68,20 @@
6668
white-space: nowrap;
6769
}
6870

71+
@mixin button-reset($width: true) {
72+
@include reset;
73+
@include helvetica;
74+
@include font-smoothing;
75+
@include letter-spacing;
76+
background: none;
77+
appearance: none;
78+
border: 0;
79+
80+
@if ($width == true) {
81+
width: 100%;
82+
}
83+
}
84+
6985
//----------------------------------------------
7086
// Deprecated
7187
// ---------------------------------------------

0 commit comments

Comments
 (0)