Published 2025-09-25.
Time to read: 4 minutes.
posts
collection.
This article is a follow-on from Microsoft Clarity Lets Me Watch You Click and Scroll.
I received the following email from Microsoft Clarity for each of my domains.
EMail From Microsoft Clarity
Subject:
Cookie Consent Requirements – updated timeline & action
|
From:
Microsoft Clarity <maccount@microsoft.com>
|
Date:
2025-08-27, 8:33 AM
|
To:
mslinn@mslinn.com
|
| ||
|
Possible Solutions
This article discusses the simplest free solution that meets Clarity’s requirements for GDPR compliance.
For advanced features (e.g., category-based consent), you would need a more complex script or a consent management platform like CookieYes, which is not free.
If your sites use a content management system, like WordPress, you can use a free plugin like Complianz (free tier) to manage the banner and call the Clarity API.
Simplest Free Solution
The following image is taken from a Clarity recording of a an anonymous Norweigan user session that uses the solution presented in this article. The orange trail shows the path of the user's mouse.
Stylistic changes can be made by editing
cookie-consent-script.js
, shown below.
To meet Microsoft Clarity’s cookie consent requirements without paying for a third-party cookie management platform (CMP) like CookieYes or relying on Google Tag Manager, you can use a custom JavaScript solution with the Clarity Consent API and a free, open-source consent banner library like CookieConsent. This approach is straightforward, and merely requires adding a consent banner, and calling Clarity’s Consent API to signal user consent for EEA/UK/Switzerland.
The CookieConsent library stores user consent on their browser for up to a year.
Implementation
Below is the step-by-step implementation guide. Excellent documentation is also available here.
Three script Tags
I had previously installed the Clarity JavaScript. Cookie consent requires two additional bits of JavaScript:
- The
cookieconsent
library (formats to 600 lines of JavaScript):Formatted cookieconsent.min.js! function(e) { if (!e.hasInitialised) { var t = { escapeRegExp: function(e) { return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") }, hasClass: function(e, t) { var i = " "; return 1 === e.nodeType && (i + e.className + i).replace(/[\n\t]/g, i).indexOf(i + t + i) >= 0 }, addClass: function(e, t) { e.className += " " + t }, removeClass: function(e, t) { var i = new RegExp("\\b" + this.escapeRegExp(t) + "\\b"); e.className = e.className.replace(i, "") }, interpolateString: function(e, t) { var i = /{{([a-z][a-z0-9\-_]*)}}/gi; return e.replace(i, function(e) { return t(arguments[1]) || "" }) }, getCookie: function(e) { var t = "; " + document.cookie, i = t.split("; " + e + "="); return i.length < 2 ? void 0 : i.pop().split(";").shift() }, setCookie: function(e, t, i, n, o, s) { var r = new Date; r.setDate(r.getDate() + (i || 365)); var a = [e + "=" + t, "expires=" + r.toUTCString(), "path=" + (o || "/")]; n && a.push("domain=" + n), s && a.push("secure"), document.cookie = a.join(";") }, deepExtend: function(e, t) { for (var i in t) t.hasOwnProperty(i) && (i in e && this.isPlainObject(e[i]) && this.isPlainObject(t[i]) ? this.deepExtend(e[i], t[i]) : e[i] = t[i]); return e }, throttle: function(e, t) { var i = !1; return function() { i || (e.apply(this, arguments), i = !0, setTimeout(function() { i = !1 }, t)) } }, hash: function(e) { var t, i, n, o = 0; if (0 === e.length) return o; for (t = 0, n = e.length; t < n; ++t) i = e.charCodeAt(t), o = (o << 5) - o + i, o |= 0; return o }, normaliseHex: function(e) { return "#" == e[0] && (e = e.substr(1)), 3 == e.length && (e = e[0] + e[0] + e[1] + e[1] + e[2] + e[2]), e }, getContrast: function(e) { e = this.normaliseHex(e); var t = parseInt(e.substr(0, 2), 16), i = parseInt(e.substr(2, 2), 16), n = parseInt(e.substr(4, 2), 16), o = (299 * t + 587 * i + 114 * n) / 1e3; return o >= 128 ? "#000" : "#fff" }, getLuminance: function(e) { var t = parseInt(this.normaliseHex(e), 16), i = 38, n = (t >> 16) + i, o = (t >> 8 & 255) + i, s = (255 & t) + i, r = (16777216 + 65536 * (n < 255 ? n < 1 ? 0 : n : 255) + 256 * (o < 255 ? o < 1 ? 0 : o : 255) + (s < 255 ? s < 1 ? 0 : s : 255)).toString(16).slice(1); return "#" + r }, isMobile: function() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) }, isPlainObject: function(e) { return "object" == typeof e && null !== e && e.constructor == Object }, traverseDOMPath: function(e, i) { return e && e.parentNode ? t.hasClass(e, i) ? e : this.traverseDOMPath(e.parentNode, i) : null } }; e.status = { deny: "deny", allow: "allow", dismiss: "dismiss" }, e.transitionEnd = function() { var e = document.createElement("div"), t = { t: "transitionend", OT: "oTransitionEnd", msT: "MSTransitionEnd", MozT: "transitionend", WebkitT: "webkitTransitionEnd" }; for (var i in t) if (t.hasOwnProperty(i) && "undefined" != typeof e.style[i + "ransition"]) return t[i]; return "" }(), e.hasTransition = !!e.transitionEnd; var i = Object.keys(e.status).map(t.escapeRegExp); e.customStyles = {}, e.Popup = function() { function n() { this.initialise.apply(this, arguments) } function o(e) { this.openingTimeout = null, t.removeClass(e, "cc-invisible") } function s(t) { t.style.display = "none", t.removeEventListener(e.transitionEnd, this.afterTransition), this.afterTransition = null } function r() { var t = this.options.onInitialise.bind(this); if (!window.navigator.cookieEnabled) return t(e.status.deny), !0; if (window.CookiesOK || window.navigator.CookiesOK) return t(e.status.allow), !0; var i = Object.keys(e.status), n = this.getStatus(), o = i.indexOf(n) >= 0; return o && t(n), o } function a() { var e = this.options.position.split("-"), t = []; return e.forEach(function(e) { t.push("cc-" + e) }), t } function c() { var e = this.options, i = "top" == e.position || "bottom" == e.position ? "banner" : "floating"; t.isMobile() && (i = "floating"); var n = ["cc-" + i, "cc-type-" + e.type, "cc-theme-" + e.theme]; e["static"] && n.push("cc-static"), n.push.apply(n, a.call(this)); p.call(this, this.options.palette); return this.customStyleSelector && n.push(this.customStyleSelector), n } function l() { var e = {}, i = this.options; i.showLink || (i.elements.link = "", i.elements.messagelink = i.elements.message), Object.keys(i.elements).forEach(function(n) { e[n] = t.interpolateString(i.elements[n], function(e) { var t = i.content[e]; return e && "string" == typeof t && t.length ? t : "" }) }); var n = i.compliance[i.type]; n || (n = i.compliance.info), e.compliance = t.interpolateString(n, function(t) { return e[t] }); var o = i.layouts[i.layout]; return o || (o = i.layouts.basic), t.interpolateString(o, function(t) { return e[t] }) } function u(i) { var n = this.options, o = document.createElement("div"), s = n.container && 1 === n.container.nodeType ? n.container : document.body; o.innerHTML = i; var r = o.children[0]; return r.style.display = "none", t.hasClass(r, "cc-window") && e.hasTransition && t.addClass(r, "cc-invisible"), this.onButtonClick = h.bind(this), r.addEventListener("click", this.onButtonClick), n.autoAttach && (s.firstChild ? s.insertBefore(r, s.firstChild) : s.appendChild(r)), r } function h(n) { var o = t.traverseDOMPath(n.target, "cc-btn") || n.target; if (t.hasClass(o, "cc-btn")) { var s = o.className.match(new RegExp("\\bcc-(" + i.join("|") + ")\\b")), r = s && s[1] || !1; r && (this.setStatus(r), this.close(!0)) } t.hasClass(o, "cc-close") && (this.setStatus(e.status.dismiss), this.close(!0)), t.hasClass(o, "cc-revoke") && this.revokeChoice() } function p(e) { var i = t.hash(JSON.stringify(e)), n = "cc-color-override-" + i, o = t.isPlainObject(e); return this.customStyleSelector = o ? n : null, o && d(i, e, "." + n), o } function d(i, n, o) { if (e.customStyles[i]) return void++e.customStyles[i].references; var s = {}, r = n.popup, a = n.button, c = n.highlight; r && (r.text = r.text ? r.text : t.getContrast(r.background), r.link = r.link ? r.link : r.text, s[o + ".cc-window"] = ["color: " + r.text, "background-color: " + r.background], s[o + ".cc-revoke"] = ["color: " + r.text, "background-color: " + r.background], s[o + " .cc-link," + o + " .cc-link:active," + o + " .cc-link:visited"] = ["color: " + r.link], a && (a.text = a.text ? a.text : t.getContrast(a.background), a.border = a.border ? a.border : "transparent", s[o + " .cc-btn"] = ["color: " + a.text, "border-color: " + a.border, "background-color: " + a.background], a.padding && s[o + " .cc-btn"].push("padding: " + a.padding), "transparent" != a.background && (s[o + " .cc-btn:hover, " + o + " .cc-btn:focus"] = ["background-color: " + (a.hover || v(a.background))]), c ? (c.text = c.text ? c.text : t.getContrast(c.background), c.border = c.border ? c.border : "transparent", s[o + " .cc-highlight .cc-btn:first-child"] = ["color: " + c.text, "border-color: " + c.border, "background-color: " + c.background]) : s[o + " .cc-highlight .cc-btn:first-child"] = ["color: " + r.text])); var l = document.createElement("style"); document.head.appendChild(l), e.customStyles[i] = { references: 1, element: l.sheet }; var u = -1; for (var h in s) s.hasOwnProperty(h) && l.sheet.insertRule(h + "{" + s[h].join(";") + "}", ++u) } function v(e) { return e = t.normaliseHex(e), "000000" == e ? "#222" : t.getLuminance(e) } function f(i) { if (t.isPlainObject(i)) { var n = t.hash(JSON.stringify(i)), o = e.customStyles[n]; if (o && !--o.references) { var s = o.element.ownerNode; s && s.parentNode && s.parentNode.removeChild(s), e.customStyles[n] = null } } } function m(e, t) { for (var i = 0, n = e.length; i < n; ++i) { var o = e[i]; if (o instanceof RegExp && o.test(t) || "string" == typeof o && o.length && o === t) return !0 } return !1 } function b() { var i = this.setStatus.bind(this), n = this.close.bind(this), o = this.options.dismissOnTimeout; "number" == typeof o && o >= 0 && (this.dismissTimeout = window.setTimeout(function() { i(e.status.dismiss), n(!0) }, Math.floor(o))); var s = this.options.dismissOnScroll; if ("number" == typeof s && s >= 0) { var r = function(t) { window.pageYOffset > Math.floor(s) && (i(e.status.dismiss), n(!0), window.removeEventListener("scroll", r), this.onWindowScroll = null) }; this.options.enabled && (this.onWindowScroll = r, window.addEventListener("scroll", r)) } var a = this.options.dismissOnWindowClick, c = this.options.ignoreClicksFrom; if (a) { var l = function(o) { for (var s = !1, r = o.path.length, a = c.length, u = 0; u < r; u++) if (!s) for (var h = 0; h < a; h++) s || (s = t.hasClass(o.path[u], c[h])); s || (i(e.status.dismiss), n(!0), window.removeEventListener("click", l), this.onWindowClick = null) }.bind(this); this.options.enabled && (this.onWindowClick = l, window.addEventListener("click", l)) } } function g() { if ("info" != this.options.type && (this.options.revokable = !0), t.isMobile() && (this.options.animateRevokable = !1), this.options.revokable) { var e = a.call(this); this.options.animateRevokable && e.push("cc-animate"), this.customStyleSelector && e.push(this.customStyleSelector); var i = this.options.revokeBtn.replace("", e.join(" ")).replace("", this.options.content.policy); this.revokeBtn = u.call(this, i); var n = this.revokeBtn; if (this.options.animateRevokable) { var o = t.throttle(function(e) { var i = !1, o = 20, s = window.innerHeight - 20; t.hasClass(n, "cc-top") && e.clientY < o && (i = !0), t.hasClass(n, "cc-bottom") && e.clientY > s && (i = !0), i ? t.hasClass(n, "cc-active") || t.addClass(n, "cc-active") : t.hasClass(n, "cc-active") && t.removeClass(n, "cc-active") }, 200); this.onMouseMove = o, window.addEventListener("mousemove", o) } } } var y = { enabled: !0, container: null, cookie: { name: "cookieconsent_status", path: "/", domain: "", expiryDays: 365, secure: !1 }, onPopupOpen: function() {}, onPopupClose: function() {}, onInitialise: function(e) {}, onStatusChange: function(e, t) {}, onRevokeChoice: function() {}, onNoCookieLaw: function(e, t) {}, content: { header: "Cookies used on the website!", message: "This website uses cookies to ensure you get the best experience on our website.", dismiss: "Got it!", allow: "Allow cookies", deny: "Decline", link: "Learn more", href: "https://cookiesandyou.com", close: "❌", target: "_blank", policy: "Cookie Policy" }, elements: { header: ' ', message: '', messagelink: '', allow: '', deny: '', link: '', close: '' }, window: '', revokeBtn: '', compliance: { info: '', "opt-in": '', "opt-out": '' }, type: "info", layouts: { basic: "", "basic-close": "", "basic-header": "" }, layout: "basic", position: "bottom", theme: "block", "static": !1, palette: null, revokable: !1, animateRevokable: !0, showLink: !0, dismissOnScroll: !1, dismissOnTimeout: !1, dismissOnWindowClick: !1, ignoreClicksFrom: ["cc-revoke", "cc-btn"], autoOpen: !0, autoAttach: !0, whitelistPage: [], blacklistPage: [], overrideHTML: null }; return n.prototype.initialise = function(e) { this.options && this.destroy(), t.deepExtend(this.options = {}, y), t.isPlainObject(e) && t.deepExtend(this.options, e), r.call(this) && (this.options.enabled = !1), m(this.options.blacklistPage, location.pathname) && (this.options.enabled = !1), m(this.options.whitelistPage, location.pathname) && (this.options.enabled = !0); var i = this.options.window.replace("", c.call(this).join(" ")).replace("", l.call(this)), n = this.options.overrideHTML; if ("string" == typeof n && n.length && (i = n), this.options["static"]) { var o = u.call(this, '
', dismiss: '' + i + ""); o.style.display = "", this.element = o.firstChild, this.element.style.display = "none", t.addClass(this.element, "cc-invisible") } else this.element = u.call(this, i); b.call(this), g.call(this), this.options.autoOpen && this.autoOpen() }, n.prototype.destroy = function() { this.onButtonClick && this.element && (this.element.removeEventListener("click", this.onButtonClick), this.onButtonClick = null), this.dismissTimeout && (clearTimeout(this.dismissTimeout), this.dismissTimeout = null), this.onWindowScroll && (window.removeEventListener("scroll", this.onWindowScroll), this.onWindowScroll = null), this.onWindowClick && (window.removeEventListener("click", this.onWindowClick), this.onWindowClick = null), this.onMouseMove && (window.removeEventListener("mousemove", this.onMouseMove), this.onMouseMove = null), this.element && this.element.parentNode && this.element.parentNode.removeChild(this.element), this.element = null, this.revokeBtn && this.revokeBtn.parentNode && this.revokeBtn.parentNode.removeChild(this.revokeBtn), this.revokeBtn = null, f(this.options.palette), this.options = null }, n.prototype.open = function(t) { if (this.element) return this.isOpen() || (e.hasTransition ? this.fadeIn() : this.element.style.display = "", this.options.revokable && this.toggleRevokeButton(), this.options.onPopupOpen.call(this)), this }, n.prototype.close = function(t) { if (this.element) return this.isOpen() && (e.hasTransition ? this.fadeOut() : this.element.style.display = "none", t && this.options.revokable && this.toggleRevokeButton(!0), this.options.onPopupClose.call(this)), this }, n.prototype.fadeIn = function() { var i = this.element; if (e.hasTransition && i && (this.afterTransition && s.call(this, i), t.hasClass(i, "cc-invisible"))) { if (i.style.display = "", this.options["static"]) { var n = this.element.clientHeight; this.element.parentNode.style.maxHeight = n + "px" } var r = 20; this.openingTimeout = setTimeout(o.bind(this, i), r) } }, n.prototype.fadeOut = function() { var i = this.element; e.hasTransition && i && (this.openingTimeout && (clearTimeout(this.openingTimeout), o.bind(this, i)), t.hasClass(i, "cc-invisible") || (this.options["static"] && (this.element.parentNode.style.maxHeight = ""), this.afterTransition = s.bind(this, i), i.addEventListener(e.transitionEnd, this.afterTransition), t.addClass(i, "cc-invisible"))) }, n.prototype.isOpen = function() { return this.element && "" == this.element.style.display && (!e.hasTransition || !t.hasClass(this.element, "cc-invisible")) }, n.prototype.toggleRevokeButton = function(e) { this.revokeBtn && (this.revokeBtn.style.display = e ? "" : "none") }, n.prototype.revokeChoice = function(e) { this.options.enabled = !0, this.clearStatus(), this.options.onRevokeChoice.call(this), e || this.autoOpen() }, n.prototype.hasAnswered = function(t) { return Object.keys(e.status).indexOf(this.getStatus()) >= 0 }, n.prototype.hasConsented = function(t) { var i = this.getStatus(); return i == e.status.allow || i == e.status.dismiss }, n.prototype.autoOpen = function(e) { !this.hasAnswered() && this.options.enabled ? this.open() : this.hasAnswered() && this.options.revokable && this.toggleRevokeButton(!0) }, n.prototype.setStatus = function(i) { var n = this.options.cookie, o = t.getCookie(n.name), s = Object.keys(e.status).indexOf(o) >= 0; Object.keys(e.status).indexOf(i) >= 0 ? (t.setCookie(n.name, i, n.expiryDays, n.domain, n.path, n.secure), this.options.onStatusChange.call(this, i, s)) : this.clearStatus() }, n.prototype.getStatus = function() { return t.getCookie(this.options.cookie.name) }, n.prototype.clearStatus = function() { var e = this.options.cookie; t.setCookie(e.name, "", -1, e.domain, e.path) }, n }(), e.Location = function() { function e(e) { t.deepExtend(this.options = {}, s), t.isPlainObject(e) && t.deepExtend(this.options, e), this.currentServiceIndex = -1 } function i(e, t, i) { var n, o = document.createElement("script"); o.type = "text/" + (e.type || "javascript"), o.src = e.src || e, o.async = !1, o.onreadystatechange = o.onload = function() { var e = o.readyState; clearTimeout(n), t.done || e && !/loaded|complete/.test(e) || (t.done = !0, t(), o.onreadystatechange = o.onload = null) }, document.body.appendChild(o), n = setTimeout(function() { t.done = !0, t(), o.onreadystatechange = o.onload = null }, i) } function n(e, t, i, n, o) { var s = new(window.XMLHttpRequest || window.ActiveXObject)("MSXML2.XMLHTTP.3.0"); if (s.open(n ? "POST" : "GET", e, 1), s.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), Array.isArray(o)) for (var r = 0, a = o.length; r < a; ++r) { var c = o[r].split(":", 2); s.setRequestHeader(c[0].replace(/^\s+|\s+$/g, ""), c[1].replace(/^\s+|\s+$/g, "")) } "function" == typeof t && (s.onreadystatechange = function() { s.readyState > 3 && t(s) }), s.send(n) } function o(e) { return new Error("Error [" + (e.code || "UNKNOWN") + "]: " + e.error) } var s = { timeout: 5e3, services: ["ipinfo"], serviceDefinitions: { ipinfo: function() { return { url: "//ipinfo.io", headers: ["Accept: application/json"], callback: function(e, t) { try { var i = JSON.parse(t); return i.error ? o(i) : { code: i.country } } catch (n) { return o({ error: "Invalid response (" + n + ")" }) } } } }, ipinfodb: function(e) { return { url: "//api.ipinfodb.com/v3/ip-country/?key={api_key}&format=json&callback={callback}", isScript: !0, callback: function(e, t) { try { var i = JSON.parse(t); return "ERROR" == i.statusCode ? o({ error: i.statusMessage }) : { code: i.countryCode } } catch (n) { return o({ error: "Invalid response (" + n + ")" }) } } } }, maxmind: function() { return { url: "//js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js", isScript: !0, callback: function(e) { return window.geoip2 ? void geoip2.country(function(t) { try { e({ code: t.country.iso_code }) } catch (i) { e(o(i)) } }, function(t) { e(o(t)) }) : void e(new Error("Unexpected response format. The downloaded script should have exported `geoip2` to the global scope")) } } } } }; return e.prototype.getNextService = function() { var e; do e = this.getServiceByIdx(++this.currentServiceIndex); while (this.currentServiceIndex < this.options.services.length && !e); return e }, e.prototype.getServiceByIdx = function(e) { var i = this.options.services[e]; if ("function" == typeof i) { var n = i(); return n.name && t.deepExtend(n, this.options.serviceDefinitions[n.name](n)), n } return "string" == typeof i ? this.options.serviceDefinitions[i]() : t.isPlainObject(i) ? this.options.serviceDefinitions[i.name](i) : null }, e.prototype.locate = function(e, t) { var i = this.getNextService(); return i ? (this.callbackComplete = e, this.callbackError = t, void this.runService(i, this.runNextServiceOnError.bind(this))) : void t(new Error("No services to run")) }, e.prototype.setupUrl = function(e) { var t = this.getCurrentServiceOpts(); return e.url.replace(/\{(.*?)\}/g, function(i, n) { if ("callback" === n) { var o = "callback" + Date.now(); return window[o] = function(t) { e.__JSONP_DATA = JSON.stringify(t) }, o } if (n in t.interpolateUrl) return t.interpolateUrl[n] }) }, e.prototype.runService = function(e, t) { var o = this; if (e && e.url && e.callback) { var s = e.isScript ? i : n, r = this.setupUrl(e); s(r, function(i) { var n = i ? i.responseText : ""; e.__JSONP_DATA && (n = e.__JSONP_DATA, delete e.__JSONP_DATA), o.runServiceCallback.call(o, t, e, n) }, this.options.timeout, e.data, e.headers) } }, e.prototype.runServiceCallback = function(e, t, i) { var n = this, o = function(t) { s || n.onServiceResult.call(n, e, t) }, s = t.callback(o, i); s && this.onServiceResult.call(this, e, s) }, e.prototype.onServiceResult = function(e, t) { t instanceof Error || t && t.error ? e.call(this, t, null) : e.call(this, null, t) }, e.prototype.runNextServiceOnError = function(e, t) { if (e) { this.logError(e); var i = this.getNextService(); i ? this.runService(i, this.runNextServiceOnError.bind(this)) : this.completeService.call(this, this.callbackError, new Error("All services failed")) } else this.completeService.call(this, this.callbackComplete, t) }, e.prototype.getCurrentServiceOpts = function() { var e = this.options.services[this.currentServiceIndex]; return "string" == typeof e ? { name: e } : "function" == typeof e ? e() : t.isPlainObject(e) ? e : {} }, e.prototype.completeService = function(e, t) { this.currentServiceIndex = -1, e && e(t) }, e.prototype.logError = function(e) { var t = this.currentServiceIndex, i = this.getServiceByIdx(t); console.warn("The service[" + t + "] (" + i.url + ") responded with the following error", e) }, e }(), e.Law = function() { function e(e) { this.initialise.apply(this, arguments) } var i = { regionalLaw: !0, hasLaw: ["AT", "BE", "BG", "HR", "CZ", "CY", "DK", "EE", "FI", "FR", "DE", "EL", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "SK", "ES", "SE", "GB", "UK", "GR", "EU"], revokable: ["HR", "CY", "DK", "EE", "FR", "DE", "LV", "LT", "NL", "PT", "ES"], explicitAction: ["HR", "IT", "ES"] }; return e.prototype.initialise = function(e) { t.deepExtend(this.options = {}, i), t.isPlainObject(e) && t.deepExtend(this.options, e) }, e.prototype.get = function(e) { var t = this.options; return { hasLaw: t.hasLaw.indexOf(e) >= 0, revokable: t.revokable.indexOf(e) >= 0, explicitAction: t.explicitAction.indexOf(e) >= 0 } }, e.prototype.applyLaw = function(e, t) { var i = this.get(t); return i.hasLaw || (e.enabled = !1, "function" == typeof e.onNoCookieLaw && e.onNoCookieLaw(t, i)), this.options.regionalLaw && (i.revokable && (e.revokable = !0), i.explicitAction && (e.dismissOnScroll = !1, e.dismissOnTimeout = !1)), e }, e }(), e.initialise = function(i, n, o) { var s = new e.Law(i.law); n || (n = function() {}), o || (o = function() {}); var r = Object.keys(e.status), a = t.getCookie("cookieconsent_status"), c = r.indexOf(a) >= 0; return c ? void n(new e.Popup(i)) : void e.getCountryCode(i, function(t) { delete i.law, delete i.location, t.code && (i = s.applyLaw(i, t.code)), n(new e.Popup(i)) }, function(t) { delete i.law, delete i.location, o(t, new e.Popup(i)) }) }, e.getCountryCode = function(t, i, n) { if (t.law && t.law.countryCode) return void i({ code: t.law.countryCode }); if (t.location) { var o = new e.Location(t.location); return void o.locate(function(e) { i(e || {}) }, n) } i({}) }, e.utils = t, e.hasInitialised = !0, window.cookieconsent = e } }(window.cookieconsent || {});
- Custom JavaScript (
cookie-consent-script.js
) to position the popup created bycookieconsent
Including the cookieconsent library into your website<script src="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.0/build/cookieconsent.min.js" data-cfasync="false"> </script>
/* Consent Banner and Clarity Consent API Integration */
window.addEventListener('load', function () {
if (window.cookieconsent && window.cookieconsent.initialise) {
window.cookieconsent.initialise({
"palette": {
"popup": {
"background": "#134376", // Background color of the consent popup
"text": "#9DDCFF" // Text color within the consent popup
},
"button": {
"background": "#9DDCFF", // Background color of the dismiss button
"text": "#134376" // Text color of the dismiss button
}
},
"position": "bottom-right",
"theme": "classic",
"type": "opt-in",
"content": {
"message": "This website uses cookies to analyze performance and provide basic functionality.",
"dismiss": "Decline",
"allow": "Accept",
"link": "Learn more",
"href": "/privacy-policy.html"
},
onInitialise: function (status) {
if (status === "allow") {
window.clarity('consent', true);
} else {
window.clarity('consent', false);
}
},
onStatusChange: function (status) {
if (status === "allow") {
window.clarity('consent', true);
} else {
window.clarity('consent', false);
}
}
});
}
});
All three of the script
tags discussed above are assembled below.
The cookieconsent
library must be loaded before the Clarity script is invoked.
- The
cookieconsent
library - My custom JavaScript file (
cookie-consent-script.js
) - The Clarity script invocation with my website ID redacted
This is how I put it all together in my Jekyll HTML template. Every page in my site incorporates this template.
<script src="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.0/build/cookieconsent.min.js" data-cfasync="false"></script> <script src="/assets/js/cookie-consent-script.js"></script> <script> (function (c, l, a, r, i, t, y) { c[a] = c[a] || function () { (c[a].q = c[a].q || []).push(arguments) }; t = l.createElement(r); t.async = 1; t.src = "https://www.clarity.ms/tag/" + i + "?ref=bwt"; y = l.getElementsByTagName(r)[0]; y.parentNode.insertBefore(t, y); })(window, document, "clarity", "script", "secretstuffhere"); </script>
The privacy policy must discuss Clarity’s analytics cookies.
A standardized stylesheet from cookieconsent
exists.
You can also use this nice form.
This stylesheet is horrible; I discuss an alternative in the Styling section below.
Test the Setup
Run your website locally and display the dialog as described below.
- Interact with the banner: Click Accept or Decline and verify behavior.
-
In browser dev tools (F12 / Application / Cookies),
confirm no Clarity cookies (e.g.,
_clck
,_clsk
) are set when consent is declined.
Deploy
- Deploy changes to each site.
- Visit each domain to simulate an EEA/UK location. You can use a VPN or a browser extension.
-
In the Clarity dashboard (
clarity.microsoft.com
), go to Settings / Consent / Check Consent Mode status (it will show green if it is working). See Microsoft’s Consent verification guide. - Test on multiple browsers (e.g., Chrome, Firefox) to ensure compatibility.
Monitor and Maintain
- Periodically check Clarity’s dashboard for consent signal errors.
- If issues arise, email clarityms@microsoft.com with your project IDs.
- Re-test before October 31, 2025, to ensure compliance.
Styling
The CSS provided for cookieconsent
is compressed; when formatted you can see how large it is.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.0/build/cookieconsent.min.css" />
Here is the formatted version of the CSS provided by cookieconsent
.
.cc-window { opacity: 1; transition: opacity 1s ease } .cc-window.cc-invisible { opacity: 0 } .cc-animate.cc-revoke { transition: transform 1s ease } .cc-animate.cc-revoke.cc-top { transform: translateY(-2em) } .cc-animate.cc-revoke.cc-bottom { transform: translateY(2em) } .cc-animate.cc-revoke.cc-active.cc-bottom, .cc-animate.cc-revoke.cc-active.cc-top, .cc-revoke:hover { transform: translateY(0) } .cc-grower { max-height: 0; overflow: hidden; transition: max-height 1s } .cc-link, .cc-revoke:hover { text-decoration: underline } .cc-revoke, .cc-window { position: fixed; overflow: hidden; box-sizing: border-box; font-family: Helvetica, Calibri, Arial, sans-serif; font-size: 16px; line-height: 1.5em; display: -ms-flexbox; display: flex; -ms-flex-wrap: nowrap; flex-wrap: nowrap; z-index: 9999 } .cc-window.cc-static { position: static } .cc-window.cc-floating { padding: 2em; max-width: 24em; -ms-flex-direction: column; flex-direction: column } .cc-window.cc-banner { padding: 1em 1.8em; width: 100%; -ms-flex-direction: row; flex-direction: row } .cc-revoke { padding: .5em } .cc-header { font-size: 18px; font-weight: 700 } .cc-btn, .cc-close, .cc-link, .cc-revoke { cursor: pointer } .cc-link { opacity: .8; display: inline-block; padding: .2em } .cc-link:hover { opacity: 1 } .cc-link:active, .cc-link:visited { color: initial } .cc-btn { display: block; padding: .4em .8em; font-size: .9em; font-weight: 700; border-width: 2px; border-style: solid; text-align: center; white-space: nowrap } .cc-highlight .cc-btn:first-child { background-color: transparent; border-color: transparent } .cc-highlight .cc-btn:first-child:focus, .cc-highlight .cc-btn:first-child:hover { background-color: transparent; text-decoration: underline } .cc-close { display: block; position: absolute; top: .5em; right: .5em; font-size: 1.6em; opacity: .9; line-height: .75 } .cc-close:focus, .cc-close:hover { opacity: 1 } .cc-revoke.cc-top { top: 0; left: 3em; border-bottom-left-radius: .5em; border-bottom-right-radius: .5em } .cc-revoke.cc-bottom { bottom: 0; left: 3em; border-top-left-radius: .5em; border-top-right-radius: .5em } .cc-revoke.cc-left { left: 3em; right: unset } .cc-revoke.cc-right { right: 3em; left: unset } .cc-top { top: 1em } .cc-left { left: 1em } .cc-right { right: 1em } .cc-bottom { bottom: 1em } .cc-floating>.cc-link { margin-bottom: 1em } .cc-floating .cc-message { display: block; margin-bottom: 1em } .cc-window.cc-floating .cc-compliance { -ms-flex: 1 0 auto; flex: 1 0 auto } .cc-window.cc-banner { -ms-flex-align: center; align-items: center } .cc-banner.cc-top { left: 0; right: 0; top: 0 } .cc-banner.cc-bottom { left: 0; right: 0; bottom: 0 } .cc-banner .cc-message { display: block; -ms-flex: 1 1 auto; flex: 1 1 auto; max-width: 100%; margin-right: 1em } .cc-compliance { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-line-pack: justify; align-content: space-between } .cc-floating .cc-compliance>.cc-btn { -ms-flex: 1; flex: 1 } .cc-btn+.cc-btn { margin-left: .5em } @media print { .cc-revoke, .cc-window { display: none } } @media screen and (max-width:900px) { .cc-btn { white-space: normal } } @media screen and (max-width:414px) and (orientation:portrait), screen and (max-width:736px) and (orientation:landscape) { .cc-window.cc-top { top: 0 } .cc-window.cc-bottom { bottom: 0 } .cc-window.cc-banner, .cc-window.cc-floating, .cc-window.cc-left, .cc-window.cc-right { left: 0; right: 0 } .cc-window.cc-banner { -ms-flex-direction: column; flex-direction: column } .cc-window.cc-banner .cc-compliance { -ms-flex: 1 1 auto; flex: 1 1 auto } .cc-window.cc-floating { max-width: none } .cc-window .cc-message { margin-bottom: 1em } .cc-window.cc-banner { -ms-flex-align: unset; align-items: unset } .cc-window.cc-banner .cc-message { margin-right: 0 } } .cc-floating.cc-theme-classic { padding: 1.2em; border-radius: 5px } .cc-floating.cc-type-info.cc-theme-classic .cc-compliance { text-align: center; display: inline; -ms-flex: none; flex: none } .cc-theme-classic .cc-btn { border-radius: 5px } .cc-theme-classic .cc-btn:last-child { min-width: 140px } .cc-floating.cc-type-info.cc-theme-classic .cc-btn { display: inline-block } .cc-theme-edgeless.cc-window { padding: 0 } .cc-floating.cc-theme-edgeless .cc-message { margin: 2em 2em 1.5em } .cc-banner.cc-theme-edgeless .cc-btn { margin: 0; padding: .8em 1.8em; height: 100% } .cc-banner.cc-theme-edgeless .cc-message { margin-left: 1em } .cc-floating.cc-theme-edgeless .cc-btn+.cc-btn { margin-left: 0 }
Displaying the Dialog
Firefox displayed the dialog fine.
I was unable to make this work on Google Chrome because
https://
was blocked by the client.
I saved the cookieconsent.min.js
file locally and referenced it from my site.
This did not help Chrome.
Then I disabled the uBlock Origin Lite extension for Chrome and the dialog appeared. Google Chrome recently changed the rules for third-party cookies and how manifests work. No-one except Alphabet (the parent company of Google) wanted this change, but Alphabet is driven by advertising revenue and is now happy to be as evil as necessary to win.
I am not going to worry about this. I expect most users will not have uBlock Origin Lite installed, and fewer people use that browser plugin every day. I have complied with the regulations, and that is enough.
Clear cookieconsent_status
The cookie consent popup is created by
cookie-consent-script.js
.
It appears only if the cookieconsent_status
cookie is not set.
To see the popup again, you must delete this cookie.
Open the browser's JavaScript console by using F12 / Console, and type the following:
document.cookie = "cookieconsent_status=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; window.location.reload();
This deletes the cookieconsent_status
cookie and reloads the page,
triggering the popup as if this is the first time you visited the page.
Wrapping this code into a function will make it easy to clear the cookieconsent_status
cookie:
function cc() { document.cookie = "cookieconsent_status=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; window.location.reload(); }
Merely type cc()
in the JavaScript console to see the cookie consent dialog.
Clear All Domain Cookies
You can also clear all cookies for the domain. This is useful if you want to test the cookie consent dialog and you do not want to manually delete each cookie. The following function clears all cookies for the domain and reloads the page.
function ccc() { document.cookie.split(";").forEach( function(c) { document.cookie = c.replace(/^ +/, "") .replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/" ); } ); window.location.reload(); }
The ccc()
function clears all cookies for the domain and reloads the page,
causing the popup to appear.