Skip to content

Commit 5bb8335

Browse files
committed
fix(header): ensure single banner role in condensed header
1 parent daf311f commit 5bb8335

File tree

2 files changed

+25
-1
lines changed

2 files changed

+25
-1
lines changed

core/src/components/header/header.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
handleToolbarIntersection,
1616
setHeaderActive,
1717
setToolbarBackgroundOpacity,
18+
getRoleType,
1819
} from './header.utils';
1920

2021
/**
@@ -210,7 +211,7 @@ export class Header implements ComponentInterface {
210211
const collapse = this.collapse || 'none';
211212

212213
// banner role must be at top level, so remove role if inside a menu
213-
const roleType = hostContext('ion-menu', this.el) ? 'none' : 'banner';
214+
const roleType = getRoleType(hostContext('ion-menu', this.el));
214215

215216
return (
216217
<Host

core/src/components/header/header.utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { readTask, writeTask } from '@stencil/core';
22
import { clamp } from '@utils/helpers';
33

44
const TRANSITION = 'all 0.2s ease-in-out';
5+
const ROLE_NONE = 'none';
6+
const ROLE_BANNER = 'banner';
57

68
interface HeaderIndex {
79
el: HTMLIonHeaderElement;
@@ -171,6 +173,7 @@ export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
171173
const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
172174

173175
if (active) {
176+
headerEl.setAttribute('role', ROLE_BANNER);
174177
headerEl.classList.remove('header-collapse-condense-inactive');
175178

176179
ionTitles.forEach((ionTitle) => {
@@ -179,6 +182,16 @@ export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
179182
}
180183
});
181184
} else {
185+
/**
186+
* There can only be one banner landmark per page.
187+
* By default, all ion-headers have the banner role.
188+
* This causes an accessibility issue when using a
189+
* condensed header since there are two ion-headers
190+
* on the page at once (active and inactive).
191+
* To solve this, the role needs to be toggled
192+
* based on which header is active.
193+
*/
194+
headerEl.setAttribute('role', ROLE_NONE);
182195
headerEl.classList.add('header-collapse-condense-inactive');
183196

184197
/**
@@ -244,3 +257,13 @@ export const handleHeaderFade = (scrollEl: HTMLElement, baseEl: HTMLElement, con
244257
});
245258
});
246259
};
260+
261+
/**
262+
* Get the role type for the ion-header.
263+
*
264+
* @param isInsideMenu If ion-header is inside ion-menu.
265+
* @returns 'none' if inside ion-menu, otherwise 'banner'.
266+
*/
267+
export const getRoleType = (isInsideMenu: boolean) => {
268+
return isInsideMenu ? ROLE_NONE : ROLE_BANNER;
269+
};

0 commit comments

Comments
 (0)