diff --git a/.changeset/cold-starfishes-sleep.md b/.changeset/cold-starfishes-sleep.md
new file mode 100644
index 0000000..6b73ca5
--- /dev/null
+++ b/.changeset/cold-starfishes-sleep.md
@@ -0,0 +1,5 @@
+---
+"html-aria": patch
+---
+
+Fix role for section element with no accessible name
diff --git a/src/get-role.ts b/src/get-role.ts
index 2654f5c..ae1d58b 100644
--- a/src/get-role.ts
+++ b/src/get-role.ts
@@ -72,6 +72,10 @@ export function getRole(element: VirtualElement | HTMLElement, options?: GetRole
case 'footer': {
return getFooterRole(options);
}
+ case 'section': {
+ const name = calculateAccessibleName({ tagName, attributes });
+ return name ? tag.defaultRole : 'generic';
+ }
case 'select': {
return getSelectRole({ attributes });
}
diff --git a/src/lib/util.ts b/src/lib/util.ts
index d277cee..51b2e80 100644
--- a/src/lib/util.ts
+++ b/src/lib/util.ts
@@ -63,9 +63,18 @@ export function calculateAccessibleName(element: VirtualElement): string | undef
switch (tagName) {
case 'img': {
- // according to spec, aria-label is technically allowed for
(even if alt is preferred)
+ /**
+ * According to spec, aria-label is technically allowed for
(even if alt is preferred)
+ * @see https://www.w3.org/TR/html-aam-1.0/#img-element-accessible-name-computation
+ */
return (attributes?.alt || attributes?.['aria-label'] || attributes?.['aria-labelledby']) as string;
}
+ case 'section': {
+ /**
+ * @see https://www.w3.org/TR/html-aam-1.0/#section-and-grouping-element-accessible-name-computation
+ */
+ return (attributes?.['aria-label'] || attributes?.['aria-labelledby']) as string;
+ }
}
}
diff --git a/test/get-role.test.ts b/test/get-role.test.ts
index bb5a3d2..9b9af26 100644
--- a/test/get-role.test.ts
+++ b/test/get-role.test.ts
@@ -79,7 +79,12 @@ describe('getRole', () => {
['html', { given: [{ tagName: 'html' }], want: 'document' }],
['i', { given: [{ tagName: 'i' }], want: 'generic' }],
['iframe', { given: [{ tagName: 'iframe' }], want: undefined }],
- ['img (name)', { given: [{ tagName: 'img', attributes: { alt: 'My image' } }], want: 'img' }],
+ ['img (named by alt)', { given: [{ tagName: 'img', attributes: { alt: 'My image' } }], want: 'img' }],
+ ['img (named by label)', { given: [{ tagName: 'img', attributes: { 'aria-label': 'My image' } }], want: 'img' }],
+ [
+ 'img (named by labelledby)',
+ { given: [{ tagName: 'img', attributes: { 'aria-labelledby': 'My image' } }], want: 'img' },
+ ],
['img (no name)', { given: [{ tagName: 'img' }], want: 'none' }],
['input', { given: [{ tagName: 'input' }], want: 'textbox' }],
['input[type=button]', { given: [{ tagName: 'input', attributes: { type: 'button' } }], want: 'button' }],
@@ -226,7 +231,15 @@ describe('getRole', () => {
['samp', { given: [{ tagName: 'samp' }], want: 'generic' }],
['script', { given: [{ tagName: 'script' }], want: undefined }],
['search', { given: [{ tagName: 'search' }], want: 'search' }],
- ['section', { given: [{ tagName: 'section' }], want: 'region' }],
+ [
+ 'section (named by label)',
+ { given: [{ tagName: 'section', attributes: { 'aria-label': 'My section' } }], want: 'region' },
+ ],
+ [
+ 'section (named by labelledby)',
+ { given: [{ tagName: 'section', attributes: { 'aria-labelledby': 'My section' } }], want: 'region' },
+ ],
+ ['section (no name)', { given: [{ tagName: 'section' }], want: 'generic' }],
['select', { given: [{ tagName: 'select' }], want: 'combobox' }],
['select[size=0]', { given: [{ tagName: 'select', attributes: { size: 0 } }], want: 'combobox' }],
['select[size=1]', { given: [{ tagName: 'select', attributes: { size: 1 } }], want: 'combobox' }],
diff --git a/test/get-supported-attributes.test.ts b/test/get-supported-attributes.test.ts
index e4289ea..e3c803a 100644
--- a/test/get-supported-attributes.test.ts
+++ b/test/get-supported-attributes.test.ts
@@ -162,14 +162,14 @@ const tests: [
['html', { given: [{ tagName: 'html' }], want: NO_ATTRIBUTES }],
['i', { given: [{ tagName: 'i' }], want: removeProhibited(GLOBAL_ATTRIBUTES, { nameProhibited: true }) }],
['iframe', { given: [{ tagName: 'iframe' }], want: GLOBAL_ATTRIBUTES }],
- ['img', { given: [{ tagName: 'img' }], want: ['aria-hidden'] }],
[
- 'img',
+ 'img (name)',
{
given: [{ tagName: 'img', attributes: { alt: 'Alt text' } }],
want: [...GLOBAL_ATTRIBUTES, ...roles.img.supported],
},
],
+ ['img (no name)', { given: [{ tagName: 'img' }], want: ['aria-hidden'] }],
['input[type=button]', { given: [{ tagName: 'input', attributes: { type: 'button' } }], want: BUTTON_ATTRIBUTES }],
[
'input[type=checkbox]',
@@ -411,7 +411,14 @@ const tests: [
['samp', { given: [{ tagName: 'samp' }], want: GENERIC_NO_NAMING }],
['script', { given: [{ tagName: 'script' }], want: NO_ATTRIBUTES }],
['search', { given: [{ tagName: 'search' }], want: [...GLOBAL_ATTRIBUTES, ...roles.search.supported] }],
- ['section', { given: [{ tagName: 'section' }], want: [...GLOBAL_ATTRIBUTES, ...roles.region.supported] }],
+ [
+ 'section (name)',
+ {
+ given: [{ tagName: 'section', attributes: { 'aria-label': 'My section' } }],
+ want: [...GLOBAL_ATTRIBUTES, ...roles.region.supported],
+ },
+ ],
+ ['section (no name)', { given: [{ tagName: 'section' }], want: GENERIC_NO_NAMING }],
['select', { given: [{ tagName: 'select' }], want: COMBOBOX_ATTRIBUTES }],
['select[size=0]', { given: [{ tagName: 'select', attributes: { size: 0 } }], want: COMBOBOX_ATTRIBUTES }],
['select[size=1]', { given: [{ tagName: 'select', attributes: { size: 1 } }], want: COMBOBOX_ATTRIBUTES }],