-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENABLE-140 Component Cookie Banner #174
Open
ocjadan
wants to merge
26
commits into
main
Choose a base branch
from
ENABLE-140-component-cookie-banner
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all 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 c6cafe8
Show simple modal and non-modal dialogs.
ocjadan 3aa5997
Cookie banner message announcing for Safari and Chrome. Not Firefox. …
ocjadan 45c7c2f
cookiebanner.php webpage contents explaining modal and non-modal dial…
ocjadan fee7b17
Cookie banner announcing appropriately when shown as a modal or non-m…
ocjadan 8759730
Provide images for the cookie banner selection under Controls in Main…
ocjadan 3fdcca0
Prevent headers inside <dialog> to get generated Table of Contents ad…
ocjadan 485b882
Code walkthrough.
ocjadan 73a7ff7
Unit tests for cookie banner.
ocjadan c308ec3
Add steps for enabling an accessible dialog.
ocjadan 9ac6d9c
Add poster jpeg for cookie banner.
ocjadan eb06df1
Restore original formatting of global.js file.
ocjadan f91b786
Re-add prevent TOC content being generated for cookie banner dialog.
ocjadan 4b74c83
Small renaming edit.
ocjadan 47e15d5
Styling for cookie banner.
ocjadan 5c82c30
Cookie banner built with <aside> tag.
ocjadan e72b019
Ensure dialog headers are not generated for table of contents.
ocjadan cb4e8c2
Remove tabIndex.
ocjadan 88cf056
cookiebanner.php does not show the exampe banner.
ocjadan 3fda260
Aside programmatically appended to end of document.
ocjadan 77007be
Refactor focus loop code into its own class. Focus loop on cookie ban…
ocjadan 8bd5749
Merge main.
ocjadan 9af7456
Update cookie banner images.
ocjadan 553cb0f
Square cookiebanner images for main menu.
ocjadan 77968eb
Fix autofocus issue with automated tests.
ocjadan 83dc65e
Fix aria-labelledby for example so it passes automated tests.
ocjadan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
<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" aria-haspopup="dialog">Show Modal Banner</button> | ||
|
||
<div id="cookie-banner-example" class="enable-example--no-border"> | ||
<dialog id="cookie-banner" 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="cookie-banner__close-button__icon" src="images/close-window.svg" alt="close cookie notice"> | ||
</button> | ||
|
||
<div role="document"> | ||
<h2 id="cookie-banner-title" class="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" class="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-example"); ?> | ||
|
||
<script type="application/json" id="cookie-banner-example-props"> | ||
{ | ||
"replaceHtmlRules": {}, | ||
"steps": [ | ||
{ | ||
"label": "Use the HTML dialog tag", | ||
"highlight": "%OPENCLOSECONTENTTAG%dialog", | ||
"notes": "The dialog tag comes with handy functionality such as a <code>.showModal()</code> method." | ||
}, | ||
{ | ||
"label": "Use a form to encapsulate the contents of the cookie banner", | ||
"highlight": "%OPENCLOSECONTENTTAG%form", | ||
"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." | ||
}, | ||
{ | ||
"label": "Ensure the form element has \"aria-labelledby\" set", | ||
"highlight": "%INLINE%aria-labelledby=\"cookie-banner-title\"", | ||
"notes": "Using aria-labelledby along with a div element with a role of document ensures it's read out when the dialog is opened." | ||
}, | ||
{ | ||
"label": "Use the document role to enclose the cookie explanation", | ||
"highlight": "%INLINE%role=\"document\"", | ||
"notes": "This presents the content in reading mode for screen readers." | ||
} | ||
] | ||
} | ||
</script> | ||
|
||
<p> | ||
With the HTML set up, use the built-in method <code>.showModal()</code> of the <code>dialog</code> HTML tag to show | ||
the cookie banner. | ||
</p> | ||
|
||
<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" aria-haspopup="dialog">Show Non-Modal Banner</button> | ||
|
||
<div id="cookie-banner-example2" class="enable-example--no-border"> | ||
<aside id="non-modal-cookie-banner-example" class="non-modal-cookie-banner__example" aria-labelledby="non-modal-cookie-banner-title-example"> | ||
<button id="non-modal-cookie-banner-close-button-example" class="cookie-banner__close-button"> | ||
<img class="cookie-banner__close-button__icon" src="images/close-window.svg" alt="close cookie notice"> | ||
</button> | ||
|
||
<div role="document"> | ||
<h2 id="non-modal-cookie-banner-title-example" class="cookie-banner__title">Cookie Notice</h2> | ||
<p id="non-modal-cookie-banner-message-example"> | ||
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="non-modal-cookie-banner-accept-button-example" class="cookie-banner__accept-button">Accept</button> | ||
<button id="non-modal-cookie-banner-reject-button-example" class="cookie-banner__reject-button">Reject</button> | ||
</div> | ||
</aside> | ||
</div> | ||
|
||
<?php includeShowcode("cookie-banner-example2"); ?> | ||
|
||
<script type="application/json" id="cookie-banner-example2-props"> | ||
{ | ||
"replaceHtmlRules": {}, | ||
"steps": [ | ||
{ | ||
"label": "Use the HTML aside tag", | ||
"highlight": "%OPENCLOSECONTENTTAG%aside", | ||
"notes": "" | ||
}, | ||
{ | ||
"label": "Ensure the aside element has \"aria-labelledby\" set", | ||
"highlight": "%INLINE%aria-labelledby=\"non-modal-cookie-banner-title\"", | ||
"notes": "Using aria-labelledby along with a div element with a role of document ensures it's read out when the dialog is opened." | ||
}, | ||
{ | ||
"label": "Use the document role to enclose the cookie explanation", | ||
"highlight": "%INLINE%role=\"document\"", | ||
"notes": "This presents the content in reading mode for screen readers." | ||
} | ||
] | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" > |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
.cookie-banner { | ||
position: fixed; | ||
bottom: 0; | ||
background-color: #333; | ||
color: white; | ||
padding: 20px; | ||
border: none; | ||
border-top: 2px solid #444; | ||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); | ||
} | ||
.cookie-banner__close-button { | ||
position: absolute; | ||
top: 0; | ||
right: 0; | ||
background: none; | ||
border: none; | ||
display: inline; | ||
} | ||
.cookie-banner__close-button__icon { | ||
border: 0; | ||
width: 38px; | ||
height: 38px; | ||
} | ||
.cookie-banner__title { | ||
flex: 1; | ||
margin-top: 0; | ||
text-align: center; | ||
color: white; | ||
font-family: "OpenSans", "Helvetica", "Arial", sans-serif; | ||
font-weight: bold; | ||
} | ||
.cookie-banner__action-buttons { | ||
display: flex; | ||
flex-direction: row; | ||
justify-content: flex-end; | ||
} | ||
.cookie-banner__accept-button { | ||
background-color: green; | ||
color: white; | ||
border: none; | ||
padding: 10px 20px; | ||
cursor: pointer; | ||
margin-left: 20px; | ||
border-radius: 5px; | ||
font-size: large; | ||
} | ||
.cookie-banner__reject-button { | ||
margin-left: 16px; | ||
background: none; | ||
color: white; | ||
border: none; | ||
padding: 10px 20px; | ||
cursor: pointer; | ||
margin-left: 20px; | ||
border-radius: 5px; | ||
font-size: large; | ||
} | ||
.cookie-banner .actionButton { | ||
color: white; | ||
border: none; | ||
padding: 10px 20px; | ||
cursor: pointer; | ||
margin-left: 20px; | ||
border-radius: 5px; | ||
font-size: large; | ||
} | ||
.non-modal-cookie-banner { | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
position: fixed; | ||
bottom: 0; | ||
left: 0; | ||
width: 100%; | ||
max-width: unset; | ||
margin: unset; | ||
background: rgba(0, 0, 0, 0.8); | ||
color: white; | ||
padding: 1em; | ||
text-align: center; | ||
z-index: 1000; | ||
} | ||
.non-modal-cookie-banner__example { | ||
display: none; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import accessibility from '../../enable-node-libs/accessibility-js-routines/dist/accessibility.module.js'; | ||
|
||
export class DialogFocusManager { | ||
previousFocus; | ||
registeredDialogs; | ||
|
||
constructor() { | ||
this.previousFocus = null; | ||
this.registeredDialogs = new WeakMap(); | ||
this.initFocusOutListener(); | ||
} | ||
|
||
initFocusOutListener() { | ||
document.addEventListener('focusout', (ev) => { | ||
this.previousFocus = ev.target; | ||
}, true); | ||
} | ||
|
||
focusOn(dialog) { | ||
if (!window.WeakMap || !window.MutationObserver) { | ||
return; | ||
} | ||
|
||
if (dialog.localName !== 'dialog') { | ||
throw new Error('Failed to upgrade focus on dialog: The element is not a dialog.'); | ||
} | ||
|
||
if (this.registeredDialogs.has(dialog)) { | ||
return; | ||
} | ||
|
||
this.registeredDialogs.set(dialog, null); | ||
this.overrideShowModal(dialog); | ||
this.observeDialogAttributes(dialog); | ||
this.addCloseEventListener(dialog); | ||
} | ||
|
||
overrideShowModal(dialog) { | ||
const realShowModal = dialog.showModal; | ||
dialog.showModal = () => { | ||
let savedFocus = document.activeElement; | ||
if (savedFocus === document || savedFocus === document.body) { | ||
savedFocus = this.previousFocus; | ||
} | ||
this.registeredDialogs.set(dialog, savedFocus); | ||
realShowModal.call(dialog); | ||
}; | ||
} | ||
|
||
observeDialogAttributes(dialog) { | ||
const mo = new MutationObserver(() => { | ||
if (dialog.hasAttribute('open')) { | ||
accessibility.setKeepFocusInside(dialog, true); | ||
} else { | ||
accessibility.setKeepFocusInside(dialog, false); | ||
} | ||
}); | ||
mo.observe(dialog, { attributes: true, attributeFilter: ['open'] }); | ||
} | ||
|
||
addCloseEventListener(dialog) { | ||
dialog.addEventListener('close', () => { | ||
if (dialog.hasAttribute('open')) { | ||
return; | ||
} | ||
const savedFocus = this.registeredDialogs.get(dialog); | ||
if (document.contains(savedFocus)) { | ||
const wasFocus = document.activeElement; | ||
savedFocus.focus(); | ||
if (document.activeElement !== savedFocus) { | ||
wasFocus.focus(); | ||
} | ||
} | ||
this.registeredDialogs.set(dialog, null); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 (witharia-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.
There was a problem hiding this comment.
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:
.show()
method which positions the contents above everything else on the page, but "still allowing interaction with content outside of the dialog". MDN linkWe'd have to program these functionalities ourselves when using the <aside> tag, and also convince users of Enable to do the same.
There was a problem hiding this comment.
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."