Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4e7f484
Cookie banner page creation and JS initialization.
ocjadan Jul 12, 2024
c6cafe8
Show simple modal and non-modal dialogs.
ocjadan Jul 15, 2024
3aa5997
Cookie banner message announcing for Safari and Chrome. Not Firefox. …
ocjadan Jul 17, 2024
45c7c2f
cookiebanner.php webpage contents explaining modal and non-modal dial…
ocjadan Jul 17, 2024
fee7b17
Cookie banner announcing appropriately when shown as a modal or non-m…
ocjadan Jul 18, 2024
8759730
Provide images for the cookie banner selection under Controls in Main…
ocjadan Jul 18, 2024
3fdcca0
Prevent headers inside <dialog> to get generated Table of Contents ad…
ocjadan Jul 18, 2024
485b882
Code walkthrough.
ocjadan Jul 18, 2024
73a7ff7
Unit tests for cookie banner.
ocjadan Jul 19, 2024
c308ec3
Add steps for enabling an accessible dialog.
ocjadan Jul 19, 2024
9ac6d9c
Add poster jpeg for cookie banner.
ocjadan Jul 19, 2024
eb06df1
Restore original formatting of global.js file.
ocjadan Jul 19, 2024
f91b786
Re-add prevent TOC content being generated for cookie banner dialog.
ocjadan Jul 19, 2024
4b74c83
Small renaming edit.
ocjadan Jul 22, 2024
47e15d5
Styling for cookie banner.
ocjadan Jul 25, 2024
5c82c30
Cookie banner built with <aside> tag.
ocjadan Jul 31, 2024
e72b019
Ensure dialog headers are not generated for table of contents.
ocjadan Jul 31, 2024
cb4e8c2
Remove tabIndex.
ocjadan Jul 31, 2024
88cf056
cookiebanner.php does not show the exampe banner.
ocjadan Aug 6, 2024
3fda260
Aside programmatically appended to end of document.
ocjadan Aug 6, 2024
77007be
Refactor focus loop code into its own class. Focus loop on cookie ban…
ocjadan Aug 6, 2024
8bd5749
Merge main.
ocjadan Aug 6, 2024
9af7456
Update cookie banner images.
ocjadan Aug 6, 2024
553cb0f
Square cookiebanner images for main menu.
ocjadan Aug 6, 2024
77968eb
Fix autofocus issue with automated tests.
ocjadan Aug 6, 2024
83dc65e
Fix aria-labelledby for example so it passes automated tests.
ocjadan Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions content/body/cookiebanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<p>
Cookie banners are ubiquitous and are often the first element on a page demanding your attention. They appear almost
instantaneously and requires a prompt decision to a lengthy explanation as to why cookies are necessary for the
webpage.
</p>

<p>
Unfortunately, many cookie banner implementations do not have the explanation interactive as text, nor do they
automatically announce that the user is now interacting with a cookie banner. Instead, screen reader users are often
guided immediately to one of the buttons—accept or reject—without knowing what it is they are accepting or rejecting.
</p>

<p>
The instructions on this page walk through how to implement an accessible cookie banner using the
<code>dialog</code> HTML element.
</p>

<h2>Modal Cookie Banner</h2>

<p>
Using a modal dialog for cookie banners causes the contents of the webpage to be unavailable—often covered by a dark
overlay—until the visitor of the webpage makes a decision on their cookie preferences. The contents of the webpage
cannot be interacted with until the modal dialog is dismissed, this is true for both sighted users and screen readers.
</p>

<br />

<button id="show-modal-button">Show Modal Banner</button>

<h2>Non-Modal Cookie Banner</h2>

<p>
Using a non-modal dialog for cookie banners allows the contents of the webpage to still be interactive. However, focus
often immediately shifts to the action buttons—accept or reject—without first announcing the contents of the dialog.
The implementation below announces the contents while also automatically focuses on the action buttons.
</p>

<br />

<button id="show-non-modal-button">Show Non-Modal Banner</button>

<div id="cookie-banner-container">
<dialog id="cookie-banner" class="cookie-banner">
Copy link
Collaborator

@zoltan-dulac zoltan-dulac Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is a non-modal, I think it better to not use the dialog tag to encapsulate the cookie banner in the non-modal case. I think

would be better (with aria-labeledby pointing to the title of the banner.

You should use for the modal case, but make sure you follow all the instructions in dialog.php.

Copy link
Collaborator Author

@ocjadan ocjadan Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @zoltan-dulac , could you share why the <aside> tag would be appropriate for a cookie banner. If we trade <dialog> in favor of <aside> then we also lose the following benefits:

  1. The .show() method which positions the contents above everything else on the page, but "still allowing interaction with content outside of the dialog". MDN link
  2. The combination of using a <form> tag with its method attribute set to "dialog", which results in automatic dismissal of the dialog when a button is pressed.

We'd have to program these functionalities ourselves when using the <aside> tag, and also convince users of Enable to do the same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zoltan: "Will check with Allison regarding the .show() method."

<form class="cookie-banner-form" method="dialog" aria-labelledby="cookie-banner-title">
<button id="cookie-banner-close-button" class="cookie-banner-close-button" autofocus>
<img class="a11y-modal__button--close-image" src="images/close-window.svg" alt="close dialog">
</button>

<div role="document" tabindex="0">
<h2 id="cookie-banner-title">Cookie Notice</h2>
<p id="cookie-banner-message">
We use strictly necessary cookies to make our Sites work. In addition, if you consent, we will use optional
functional, performance and targeting cookies to help us understand how people use our website, to improve your
user experience and to provide you with targeted advertisements. You can accept all cookies, or click to review
your cookie preferences.
</p>
</div>

<div class="cookie-banner-action-buttons">
<button id="cookie-banner-accept-button">Accept</button>
<button id="cookie-banner-reject-button" class="cookie-banner-reject-button">Reject</button>
</div>
</form>
</dialog>
</div>

<?php includeShowcode("cookie-banner-container"); ?>

<script type="application/json" id="cookie-banner-container-props">
{
"replaceHtmlRules": {},
"steps": [
{
"label": "Use a form and ensure it has its aria-labelledby attribute set",
"highlight": "aria-labelledby",
"notes": "Using a form is good practice—<a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#usage_notes\">MDN docs</a>—to have the dialog close automatically when a button within it is pressed. Using aria-labelledby along with using a div element with a role of document (next step) ensures it's read out when the dialog is opened."
},
{
"label": "Use the document role to enclose the cookie explanation",
"highlight": "role",
"notes": "This presents the content in reading mode for screen readers."
}
]
}
</script>

<p>
With the HTML set up, use the built-in methods <code>.showModal()</code> or <code>.show()</code> for the
<code>dialog</code> HTML tag to show a modal or a non-modal dialog, respectively.
</p>
6 changes: 6 additions & 0 deletions content/bottom/cookiebanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script id="cookie-banner-js" type="module">
import cookieBanner from "./js/modules/cookiebanner.js"
import showCode from "./js/enable-libs/showcode.js";
cookieBanner.init();
showCode.addJsObj('cookie banner', cookieBanner);
</script>
1 change: 1 addition & 0 deletions content/head/cookiebanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<link id="cookiebanner-css" rel="stylesheet" type="text/css" href="css/cookiebanner.css" >
1 change: 1 addition & 0 deletions css-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ css/bottom-fixed-nav.css
css/button.css
css/checkbox.css
css/combobox.css
css/cookiebanner.css
css/date.css
css/definition-term.css
css/deque-table-sortable.css
Expand Down
24 changes: 24 additions & 0 deletions css/cookiebanner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.cookie-banner {
position: fixed;
bottom: 0;
z-index: 1000;
}
.cookie-banner-form {
display: flex;
flex-direction: column;
}
.cookie-banner-close-button {
position: absolute;
top: 0;
right: 0;
max-width: 32px;
max-height: 32px;
}
.cookie-banner-action-buttons {
display: flex;
flex-direction: row;
margin-left: auto;
}
.cookie-banner-reject-button {
margin-left: 16px;
}
Binary file added images/main-menu/cookiebanner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/main-menu/cookiebanner.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/posters/cookiebanner.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions js/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ function initEnable() {
// 2) it does not have an ancestor with class no-permalink-headings
if (
el.closest('.enable-example') === null &&
el.closest('.no-permalink-headings') === null
el.closest('.no-permalink-headings') === null &&
el.closest('.cookie-banner') === null
) {
createPermalinksForHeading(el, headingIndex, true);
}
Expand All @@ -88,7 +89,7 @@ function initEnable() {
skipPages: ['/index.php', '/faq.php'],
showAsSidebarDefault: true,
numberFirstLevelHeadings: true,
selectorToSkipHeadingsWithin: '.enable-example',
selectorToSkipHeadingsWithin: '.enable-example, .cookie-banner',
collapseNestedHeadingsAfterLevel: 2,
});
}
Expand Down
27 changes: 27 additions & 0 deletions js/modules/cookiebanner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use-strict'

const cookieBanner = new function() {
let cookieBanner;

this.init = function() {
cookieBanner = document.getElementById('cookie-banner');
setUpModalButton();
setUpNonModalButton();
}

function setUpModalButton() {
const showModalButton = document.getElementById('show-modal-button');
showModalButton.addEventListener('click', () => {
cookieBanner.showModal();
});
}

function setUpNonModalButton() {
const showNonModalButton = document.getElementById('show-non-modal-button');
showNonModalButton.addEventListener('click', () => {
cookieBanner.show();
})
}
}

export default cookieBanner;
25 changes: 25 additions & 0 deletions js/modules/es4/cookiebanner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use-strict'

const cookieBanner = new (function() {
let cookieBanner;

this.init = function() {
cookieBanner = document.getElementById('cookie-banner');
setUpModalButton();
setUpNonModalButton();
}

function setUpModalButton() {
const showModalButton = document.getElementById('show-modal-button');
showModalButton.addEventListener('click', () => {
cookieBanner.showModal();
});
}

function setUpNonModalButton() {
const showNonModalButton = document.getElementById('show-non-modal-button');
showNonModalButton.addEventListener('click', () => {
cookieBanner.show();
})
}
})
53 changes: 53 additions & 0 deletions js/test/cookiebanner.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import config from './test-config';

let cookieBanner;

beforeAll(async () => {
await page.goto(`${config.BASE_URL}/cookiebanner.php`);
cookieBanner = await page.$('.cookie-banner');
});

describe('Tests for cookie banner', () => {
it('Contains div with a document role', async () => {
let result = await cookieBanner.$('div[role="document"]');
expect(result).not.toBeNull();
});

it('Contains close button', async () => {
let result = await getCloseButton();
console.log(result);
expect(result).not.toBeNull();
});

it('Has autofocus set to the close button', async () => {
let closeButton = await getCloseButton();
let result = await page.evaluate((closeButton) => closeButton.hasAttribute('autofocus'), closeButton);
expect(result).toBe(true);
});

async function getCloseButton() {
return await cookieBanner.$('button[id$="close-button"]');
}

it('Contains form with method of dialog', async () => {
let result = await getForm();
expect(result).not.toBeNull();
});

it('Has aria-labelledby set for form', async () => {
let form = await getForm();
let result = await page.evaluate((form) => form.hasAttribute('aria-labelledby'), form);
expect(result).toBe(true);
});

it('Contains element for aria-labelledby', async () => {
let form = await getForm();
let ariaLabelledBy = await page.evaluate((el) => el.getAttribute('aria-labelledby'), form);
let result = await cookieBanner.$(`[id$="${ariaLabelledBy}"]`);
expect(result).not.toBeNull();
});

async function getForm() {
return await cookieBanner.$('form[method="dialog"]');
}
});
28 changes: 28 additions & 0 deletions less/cookiebanner.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.cookie-banner {
position: fixed;
bottom: 0;
z-index: 1000; // Ensure the non-modal dialog is above other content
}

.cookie-banner-form {
display: flex;
flex-direction: column;
}

.cookie-banner-close-button {
position: absolute;
top: 0;
right: 0;
max-width: 32px;
max-height: 32px;
}

.cookie-banner-action-buttons {
display: flex;
flex-direction: row;
margin-left: auto;
}

.cookie-banner-reject-button {
margin-left: 16px;
}
1 change: 1 addition & 0 deletions sitemap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ https://www.useragentman.com/enable/button.php
https://www.useragentman.com/enable/carousel.php
https://www.useragentman.com/enable/checkbox.php
https://www.useragentman.com/enable/combobox.php
https://www.useragentman.com/enable/cookiebanner.php
https://www.useragentman.com/enable/description-list.php
https://www.useragentman.com/enable/dialog.php
https://www.useragentman.com/enable/dropdown.php
Expand Down
6 changes: 5 additions & 1 deletion templates/data/meta-info.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
"desc": "One exception to the First Rule of ARIA is HTML5 autocomplete using the datalist tag, which is not as accessible as the ARIA datalist role",
"isNPM": "true"
},
"cookiebanner.php": {
"title": "Accessible Cookie Banners",
"desc": "Blocking and non-blocking accessible cookie banners."
},
"date.php": {
"title": "Accessible Date Widget",
"desc": "Depending on the browser, the HTML5 <input type=\"date\"> is not keyboard/screen reader friendly. What is a web developer to do? ",
Expand Down Expand Up @@ -294,4 +298,4 @@
"title": "Bookmarklets to Test Accessibility Features On A Page",
"desc": "The bookmarklets on this page are designed to help developers check if certain features are coded correctly on a webpage."
}
}
}
10 changes: 9 additions & 1 deletion templates/includes/documentation-header.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,14 @@ class="enable-flyout enable-flyout__level enable-flyout__dropdown">
"alt": ""
}
},
{
"id": "flyout__link",
"props": {
"label": "Cookie Banner",
"url-slug": "cookiebanner",
"alt": ""
}
},
{
"id": "flyout__link",
"props": {
Expand Down Expand Up @@ -696,4 +704,4 @@ class="enable-flyout enable-flyout__level enable-flyout__dropdown">
</script>


</div>
</div>