Cookie Alert

In order to be legally on the safe side, the cookie alert must be used in most cases. This modal informs the user about cookies and also gives the user the possibility to set his personal cookie preferences. This cookie alert is designed as an overlay because it is legally required that the user first interacts with this element before he can use the site.

This component uses the button component and the checkbox of the form component. Therefore you have to include the CSS of these both components in order to get the cookie alert displayed correctly!

preview

<dialog role="dialog" class="cookie-alert" lang="en" dir="lr" data-controller="cookie-alert"
    aria-labelledby="087d5192" aria-describedby="7b84810b">
    <div class="cookie-alert-modal" aria-modal="true">
        <h2 class="cookie-alert-title" id="087d5192">Title</h2>
        <p class="cookie-alert-description" id="7b84810b">At vero eos et accusam et justo duo
            dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum
            dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
            eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
            vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
            takimata sanctus est Lorem <a href="#">ipsum dolor sit amet.</a>
        </p>
        <div class="cookie-alert-controls">
            <button type="button" class="cookie-alert-button" tabindex="1"
                data-controller="cookie-alert/button/accept">Accept all</button>
            <a href="#" class="cookie-alert-detail-link" tabindex="2"
                data-controller="cookie-alert/detail-link" aria-controls="08b65029"
                id="04211ef6">More details</a>
        </div>
        <div class="cookie-alert-configuration" data-controller="cookie-alert/configuration"
            aria-controls="08b65029" aria-labelledby="04211ef6" aria-expanded="false" id="08b65029">
            <div class="cookie-alert-configuration-settings">
                <div class="cookie-alert-configuration-control">
                    <input type="checkbox" class="cookie-alert-configuration-input" id="necessary"
                        tabindex="2" checked disabled />
                    <label for="necessary" class="cookie-alert-checkbox-label">Necessary</label>
                </div>
                <div class="cookie-alert-configuration-control">
                    <input type="checkbox" class="cookie-alert-configuration-input" id="preferences"
                        tabindex="2" />
                    <label for="preferences" class="cookie-alert-checkbox-label">Preferences</label>
                </div>
                <div class="cookie-alert-configuration-control">
                    <input type="checkbox" class="cookie-alert-configuration-input" id="statistics"
                        tabindex="2" />
                    <label for="statistics" class="cookie-alert-checkbox-label">Statistics</label>
                </div>
                <div class="cookie-alert-configuration-control">
                    <input type="checkbox" class="cookie-alert-configuration-input" id="marketing"
                        tabindex="2" />
                    <label for="marketing" class="cookie-alert-checkbox-label">Marketing</label>
                </div>
            </div>
            <button type="button" class="cookie-alert-button-secondary" tabindex="2"
                data-controller="cookie-alert/button/configuration">Accept configuration</button>
        </div>
    </div>
</dialog>
if (typeof window.cake !== "object") {
    window.cake = {};
}

window.cake.cookie = {
    //Add Event Listeners
    _eventListeners: [],
    _forceFocus: true,
    _addEventListener: function(element, listenerType, listenerFunction) {
        window.cake.cookie._eventListeners.push({
            element: element,
            listenerFunction: listenerFunction,
            listenerType: listenerType
        });
        element.addEventListener(listenerType, listenerFunction);
    },
    _removeAllEventListeners: function() {
        window.cake.cookie._eventListeners.forEach(function(listenerConfig) {
            listenerConfig.element.removeEventListener(listenerConfig.listenerType,
                listenerConfig.listenerFunction);
        });
    },
    // Add custom polyfills / utilities
    _addPolyfills: function() {
        if (window.NodeList && !NodeList.prototype.forEach) {
            NodeList.prototype.forEach = Array.prototype.forEach;
        }
    },
    //Set cookie settings and hide cookie
    acceptCookies: function(optinPreferences, optinStatistics, optinMarketing) {
        if (window.Cookiebot) {
            window.Cookiebot.submitCustomConsent(optinPreferences, optinStatistics,
                optinMarketing);
        }
        window.cake.cookie.hideCookieAlert();
    },
    //Show and initialize cookie alert
    showCookieAlertWithoutForcedFocus: function() {
        return window.cake.cookie.showCookieAlert(false);
    },
    showCookieAlert: function(forceFocus) {
        window.cake.cookie._addPolyfills();

        if (forceFocus === undefined) {
            forceFocus = true;
        }
        window.cake.cookie._forceFocus = forceFocus;

        // Parse cookie-alert element
        var cookieAlert = document.querySelector("*[data-controller='cookie-alert']");
        //If not cookie-alert is available, just skip
        if (!cookieAlert || cookieAlert.classList.contains("opened")) {
            return;
        }

        // Parse relevant elements
        var cookieAlertModal = cookieAlert.childNodes[0];
        var acceptAllButton = document.querySelector(
            "*[data-controller='cookie-alert/button/accept']");
        var acceptConfigButton = document.querySelector(
            "*[data-controller='cookie-alert/button/configuration']");
        var showDetailsLink = document.querySelector(
            "*[data-controller='cookie-alert/detail-link']");
        var configurationDiv = document.querySelector(
            "*[data-controller='cookie-alert/configuration']");
        var closeElements = document.querySelectorAll(
            "*[data-controller='cookie-alert/button/close']");

        //update overlay/alert size depending on viewport
        setTimeout(function() {
            window.cake.cookie._updateOverlaySize(cookieAlert, acceptAllButton,
                true);
        }, 100);

        window.cake.cookie._addEventListener(acceptAllButton, "click", function() {
            window.cake.cookie.acceptCookies(true, true, true);
        });

        window.cake.cookie._addEventListener(acceptConfigButton, "click", function() {
            var preferenceCookies = document.getElementById("preferences")
                .checked || false;
            var statisticsCookies = document.getElementById("statistics").checked ||
                false;
            var marketingCookies = document.getElementById("marketing").checked ||
                false;
            window.cake.cookie.acceptCookies(preferenceCookies, statisticsCookies,
                marketingCookies);
        });

        window.cake.cookie._addEventListener(showDetailsLink, "click", function() {
            configurationDiv.classList.toggle("expanded");
            showDetailsLink.classList.toggle("expanded");

            if (configurationDiv.classList.contains("expanded")) {
                // Make acceptAllButton disabled
                acceptAllButton.disabled = true;

                //Adjust details-text, if texts are available
                if (window.CookieConsent && window.CookieConsent.dialog) {
                    showDetailsLink.innerHTML = window.CookieConsent.dialog
                        .hideDetailsText;
                }
                configurationDiv.setAttribute("aria-expanded", "true");
                cookieAlertModal.scrollTop = cookieAlertModal.scrollHeight;
            } else {
                // Make acceptAllButton enabled
                acceptAllButton.disabled = false;

                //Adjust details-text, if texts are available
                if (window.CookieConsent && window.CookieConsent.dialog) {
                    showDetailsLink.innerHTML = window.CookieConsent.dialog
                        .showDetailsText;
                }
                configurationDiv.setAttribute("aria-expanded", "false");
            }
        });

        // Close elements event Listener
        closeElements.forEach(function(closeElement) {
            window.cake.cookie._addEventListener(closeElement, 'click', function(
            e) {
                e.preventDefault();
                window.cake.cookie.acceptCookies(false, false, false);
            });
        });

        //Display cookie alert
        cookieAlert.showModal = cookieAlert.showModal || function() {};
        cookieAlert.showModal(); // Native Browser-Method for the dialog-element
        cookieAlert.setAttribute('open', 'open');
        cookieAlert.classList.add("opened");
        cookieAlert.style.display = "block";
    },
    //Deconstruct cookie alert
    hideCookieAlert: function() {
        var cookieAlert = document.querySelector("*[data-controller='cookie-alert']");

        //Hide cookie alert
        cookieAlert.close = cookieAlert.close || function() {};
        cookieAlert.close(); // Native Browser-Method for the dialog-element
        cookieAlert.removeAttribute('open');
        cookieAlert.classList.remove("opened");
        cookieAlert.style.display = "none";

        //update overlay/alert size to previous values
        window.cake.cookie._revertOverlaySize();

        //Remove eventListeners
        window.cake.cookie._removeAllEventListeners();
    },
    // bugfix - oldBrowser - Safari iOS viewport is initially bigger than the visible part (https://medium.com/@susiekim9/how-to-compensate-for-the-ios-viewport-unit-bug-46e78d54af0d)
    _tmpStylings: [],
    _updateOverlaySize: function(cookieAlert, acceptAllButton, setEventListener) {
        if (setEventListener === true && window.cake.cookie._tmpStylings.length < 1) {
            window.cake.cookie._tmpStylings.push({
                el: document.body,
                val: document.body.style.overflow,
                attr: "overflow"
            });
            window.cake.cookie._tmpStylings.push({
                el: document.body,
                val: document.body.style.height,
                attr: "height"
            });
            window.cake.cookie._tmpStylings.push({
                el: document.documentElement,
                val: document.documentElement.style.overflow,
                attr: "overflow"
            });
            window.cake.cookie._tmpStylings.push({
                el: document.documentElement,
                val: document.documentElement.style.height,
                attr: "height"
            });

            window.cake.cookie._addEventListener(window, "resize", function() {
                //On resize or orientation switch, update the size of the alert
                window.cake.cookie._updateOverlaySize(cookieAlert);
            }.bind(cookieAlert));

            //Keep focus inside the cookie-alert element, if option is set to true
            if (window.cake.cookie._forceFocus) {
                cookieAlert.querySelectorAll("button,a,input").forEach(function(element) {
                    window.cake.cookie._addEventListener(element, "focusout",
                        function() {
                            setTimeout(function() {
                                //Prevent focus from jumping out of cookie-alert elements
                                if (!cookieAlert.contains(document
                                        .activeElement)) {
                                    acceptAllButton.focus();
                                }
                            }, 20);
                        });
                });

                document.querySelectorAll('[tabindex="1"]').forEach(function(element) {
                    if (!cookieAlert.contains(element)) {
                        element.dataset.oldTabIndex = "1";
                        element.setAttribute('tabindex', 0);
                    }
                });
                document.querySelectorAll('[tabindex="2"]').forEach(function(element) {
                    element.dataset.oldTabIndex = "2";
                    element.setAttribute('tabindex', 0);
                });
            }

        }

        cookieAlert.style.height = window.innerHeight + "px";

        document.body.style.overflow = "hidden";
        document.body.style.height = window.innerHeight + "px";

        document.documentElement.style.overflow = "hidden";
        document.documentElement.style.height = window.innerHeight + "px";
    },
    _revertOverlaySize: function() {
        window.cake.cookie._tmpStylings.forEach(function(tmpStyling) {
            tmpStyling.el.style[tmpStyling.attr] = tmpStyling.val;
        });
        if (window.cake.cookie._forceFocus) {
            document.querySelectorAll('[data-old-tab-index="1"]').forEach(function(
            element) {
                element.removeAttribute("data-old-tab-index");
                element.setAttribute('tabindex', 1);
            });
            document.querySelectorAll('[data-old-tab-index="2"]').forEach(function(
            element) {
                element.removeAttribute("data-old-tab-index");
                element.setAttribute('tabindex', 2);
            });
        }
    }
};

The cookie alert uses the <dialog> element. In order to get the correct styling applied to it, you have to use the .cookie-alert class. This dialog element is the transparent black background. Inside this <dialog> element you should create a <div> with the class .cookie-alert-modal applied to it. This is the wrapper element for all the content elements of this component:

  • title as a <h2> element with the class .h5
  • description as a <p> tag and the class .cookie-alert-description
  • more-details link .cookie-alert-detail-link
  • accept all cookies button
  • the cookie configuration collapsible <div> .cookie-alert-configuration
    • the checkboxes wrapper <div> .cookie-alert-configuration-settings
      • the labels of each checkbox .cookie-alert-checkbox-label
    • the accept configuration button

The detailed structure of this component can be seen in the above example. Best practice is to place the cookie alert component as the first element in the body of each page. Additionaly it is mandatory to place a link to your cookie-information page into the description text! Be aware, that on the cookie-information page there shouldn't be any cookie-alert. Because otherwise the visitor is not able to read the information before accepting anything. Thus you should not save or use any cookies on this page because the visitor has not accepted anything.

Accessibility#

Because this component is a legal requirement, special attention should be paid to good accessibility! One important thing is to not use a <div> but a <dialog> element to tell especially screenreaders that this element is an overlay which stays in front of other content. Also some aria-attributes should be added. The aria-labelledby and the aria-describedby attributes should reference to the appropriate element in the modal (title and description). Also the special open attribute of the <dialog> element should get set correctly. You can find a description of this html element on it's dedicated page on the MDN web docs. It's also a good practice to set the role element (role="dialog") for supporting browsers. The modal div (.cookie-alert-modal) has the attribute aria-modal="true" attatched to it.

For the collapsable cookie configuration at the bottom of the cookie dialog, you should add aria-controls="id". The configuration (.cookie-alert-configuration) needs also some additional attributes for accessibility: aria-controls="id", aria-labelledby="id", aria-expanded="false". What these attributes are doing can be read in the two examples W3: dialog-modal and W3: accordion.

To also get the correct tab order for users only using their keyboard or other tools, the tabindex of the accept all button should be set to tabindex="1" and all the other clickable elements of the cookie alert to tabindex="2". So the user first must tab through the cookie alert, before accessing the website itself. The accept all button has tabindex 1 because this element should have the inital focus.

JavaScript#

With our JavaScript we have focused on the usage in CookieBot because this is the most used tool by our users. Because of that we have written this component's JavaScript in the "old-fashioned" way with ES5 syntax. This way you can simply copy paste our JavaScript into your CookieBot console. But more to this further below.

To make your HTML work with our JavaScript, you have to apply the coorect data-controllers. Every element, that causes some JavaScript code execution needs one of the following attributes:

  • the <dialog> element should have the attribute data-controller="cookie-alert" attatched to it
  • the more details link .cookie-alert-detail-link has the attribute data-controller="cookie-alert/detail-link"
  • for the accept all button data-controller="cookie-alert/button/accept"
  • the configuration element .cookie-alert-configuration needs the data-controller="cookie-alert/configuration" attribtue attatched
  • the accept configuration button has the attribute data-controller="cookie-alert/button/configuration"
  • the close buttons or links must have the attribute data-controller="cookie-alert/button/close"

Our JavaScript parses the elements with the above mentioned data-attributes and adds three click event listener to the two buttons (accept all and accept configuration) and to the more details link. These click event listeners implement some functionality like setting up the appropriate accessibility attributes or disabling the primary CTA button when showing more details. The JavaScript of this component exposes three methods that can be used:

Accept cookies#

window.cake.cookie.acceptCookies: function (optinPreferences, optinStatistics, optinMarketing) {…}. This method simply saves the configuration set by the user. If the user for example clicks the accept all button, all three parameters are set to true:

acceptAllButton.addEventListener ("click", function () {
    window.cake.cookie.acceptCookies (true, true, true);
});

Show cookie alert#

window.cake.cookie.showCookieAlert: function () {…}. With this method you can show the cookie alert and initialize all the event listeners needed by this component.

window.cake.cookie.showCookieAlert (forcedFocus = true);

This method has also an optional property forcedFocus, which can force the customers browser to keep focus on the relevant elements of our cookie alert. This property is set to true as default but if forcedFocus is set to false, it will not change anything in the default focus handling of the browser.

Hide cookie alert#

window.cake.cookie.hideCookieAlert: function () {…} This method simply hides the cookie alert and removes all events added in the function above. But please be sure to save the cookie configuration before with the first method mentioned!

window.cake.cookie.hideCookieAlert ();

CookieBot integration#

We have developed our cookie alert especially for CookieBot, visit this dokumentaion if you are interestet in using our CookieBot Theme.

OneTrust integration#

We also have developed a CAKE Theme for OneTrust.

Change log#

6.10.0 - 2022-11-14#

Changed#

  • Doc: "Cookie alert" | moved CookieBot theme and OneTrust theme to Customization

6.2.0 - 2021-08-19#

Added#

  • Doc: "Cookie alert" | added javaScript file content as "JS" tab

4.0.0 - 2020-06-25#

Changed#

  • SCSS, JS, HTML: "Cookie alert" | Renamed cookie-alert-extended to cookie-alert in all classes, javascript-methods, data-controllers and files.
  • JS: "Cookie alert" | Disable primary CTA button on expanded details

Removed#

  • SCSS, JS, HTML: "Cookie alert" | Removed deprecated cookie-alert in all classes, javascript-methods, data-controllers and files.

3.9.0 - 2020-01-16#

Deprecated#

  • "Cookie alert" | The simple version with notice banner will be deprecated in future releases. Due to legal requirements the extended cookie alert with settings must be used.

3.8.0 - 2019-11-07#

Added#

  • SCSS, JS, HTML: "Cookie alert" | Added extended overlay version of cookie alert with configuration menu

Fixed#

  • SCSS: "Cookie alert" | Added !default to $cookie-alert-color, $cookie-alert-bg-color, $cookie-alert-font-size in _variables.scss file.