Introducing our cookie consent banner, Crumbs!
28 Jul 2022 · 7 min read · Open Source
Cookies. Most of us love eating them but most likely these days we are either accepting or customising them to use most of our favourite websites. With Krystal having multiple different products under our umbrella we often have the need to have a consistent approach to certain aspects of the application/website and cookie consent notices neatly falls into this bracket.
With some recent updates that we were planning to make to Katapult, we took the opportunity to build out our own cookie consent add-on that could be customised depending on a site's requirements.
After some initial research into current cookie consent implementations we noticed that they tend to be on the bulkier side in terms of resource size therefore building something lightweight became our first priority.
Other factors that we wanted to incorporate in the add-on were:
- Completely customisable in terms of styling.
- Different types of cookies could be defined depending on the use case.
Enter Crumbs! Let's walk through how to get started with Crumbs and how we are using it on Katapult.
If you want to dive into the code yourself then you can head over to the Github repo.
Installation
npm install -S krystal-crumbs
Usage
Once installed you can import and initialise Crumbs
by doing the following:
import Crumbs from "krystal-crumbs";
const crumbs = new Crumbs();
Along with some basic options such as providing a custom cookie name and the length of time the cookie should be stored for we will explore how to add different types of cookies depending on your needs.
This is done by passing in an array of objects named types
to the Crumbs constructor which then in turn requires certain properties. Here is what the types
array could look like:
const crumbs = new Crumbs({
// ...
types: [
{
identifier: "functional",
summary:
"There are a number of cookies we need to use in order for Katapult to work properly.",
title: "Functional",
},
{
identifier: "analytics",
required: false,
summary:
"We use Google Analytics to measure the performance of our website. We do not store any personal data and your IP address is anonymised. Analytics is disabled in the application itself.",
title: "Analytics",
},
{
identifier: "chat",
required: false,
summary:
"We use a live chat service called Crisp so we can provide support to you where available. Various cookies are stored so chats remain active when you change page.",
title: "Live Chat",
},
]
})
These are custom consent types, however Crumbs supports all the consent types supported by Google's Consent Mode V2 out of the box. You can simply not provide this type
option, and the following consent types will be used:
ad_personalization
ad_storage
ad_user_data
analytics_storage
functionality_storage
personalization_storage
security_storage
Alternatively, if you only want specific types, you can simply pass them like so:
const crumbs = new Crumbs({
types: [
{
identifier: "ad_storage"
}
]
});
Crumbs will automatically populate the title and summary values for you, however you can override them if you want to.
Example
Within your own application you can listen to the save
event and do as you wish with the accepted consent types. In our example we are going to simply display what types the user has accepted on the screen.
crumbs.on("save", (accepted) => {
const output = document.getElementById("output");
const formatter = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
output.textContent = formatter.format(accepted);
});
When the user accepts their chosen cookie settings then Crumbs will set a cookie with the accepted types, prefixed by a version number.
Let's take the example above and accept functional
, analytical
but reject chat
, the value will be as follows:
v1|functional|analytical
Crumbs has full support for Consent Mode V2, and will automatically broadcast the default consent state on load and any subsequent update events whenever a user changes their preferences.
Along with the other options that you can provide Crumbs, then you will end up with something that will look like the following (when the edit modal is open).
The styling will be dependent on yourself, however let's touch on how some simple CSS declarations can help you customise Crumbs to fit your needs.
Styling
The styling of Crumbs has been deliberately left up to yourself apart from only some basic positioning, which you can override if required.
On top of this there are also various different CSS custom properties that you can set in order to easily customise the appearance of not only the cookie banner but also the consent modal that is used when a user would like to adjust their cookie settings.
.crumbs-banner {
--crumbs-bg-color: #f4f4f4;
--crumbs-edit-overlay: rgba(0, 0, 0, 0.4);
--crumbs-edit-cta-bg: gainsboro;
--crumbs-toggle-switch-bg: #858585;
--crumbs-toggle-checked-bg: #e0e0e0;
--crumbs-toggle-border-color: #dddddd;
--crumbs-toggle-switch-focus-border-color: #636363;
}
These could also easily be set to custom properties themselves. Let's take the example of the --crumbs-bg-color
. You could set this to something like the following example:
:root {
--bg-color: whitesmoke;
}
.crumbs-banner {
--crumbs-bg-color: var(--bg-color);
}
This can make it a little easier to manage any sort of theming that you have in your application/website such as a dark or high contrast mode.
Now that we have walked through a detailed example of how Crumbs can be applied to an existing site/application, let's dig a little deeper into the inner workings of the library!
Under the Hood
At its core, Crumbs
is a JavaScript class which on initialisation sets up some required properties and calls our initial render method which in our case handles the insertion of the banner into the DOM as well as adding a few different event listeners that we need for the acceptance of cookies and for giving the option to edit the default settings.
Here is what our base looks like:
class Crumbs extends EventEmitter {
constructor({ name, domain, days, editCookieButton, types }) {
super();
this.name = name || "crumbs";
this.domain = domain;
this.days = days;
this.editCookieButton = editCookieButton;
this.types = types;
this.render();
}
// ...
}
Editing Cookies
As well as being able to accept all cookies it is especially important that we allow users to change their preferences at any time. Crumbs lets you do this by passing in reference to a button element in your application.
If we revisit our previous example when initialising except this time we pass in a DOM element thats job will be to handle the editing of our cookie settings.
const editButton = document.querySelector(".your-button-element");
const cookies = new Crumbs({
// other declarations
editCookieButton: editButton
});
After we give this option to the user then we will also need a way to update the preferences and let our application know that the cookie preferences of the user have been updated. Below is a compressed look at how we do this in Crumbs.
acceptCookies() {
const checkboxes = [
...this.editScreen.querySelectorAll(`input[type="checkbox"]`)
];
const accepted = checkboxes
.filter((checkbox) => checkbox.checked)
.map((checkbox) => checkbox.name);
this.emit("save", accepted);
// ...
}
Here we are selecting all the checkboxes that exist within our edit screen, filtering out the ones that are checked
and then extracting the name of each of these into a new array of accepted
values which we then pass onto our emit
method which is inherited from the EventEmitter
class.
Trapping Focus
With our implementation utilising a modal, as part of its approach then it is important for keyboard users that we trap the focus within the popup when the edit modal is open.
We can get started on adding this with the following code:
const focusable = [
...this.editScreen.querySelectorAll("button, input")
].filter((element) => !element.hasAttribute("disabled"));
const first = focusable[0];
const last = focusable[focusable.length - 1];
Due to the structure of our modal we know in advance that this will only contain input and button types therefore we don't need to select any a
tags or any other focusable element. We also filter out any elements that have the attribute of disabled
as these are not able to be focused. Now that we have an array of focusable elements we can easily select the first and last elements which we will need in order to deal with different operations that a keyboard user could perform when tabbing through our modal.
In the below snippet we have a guard clause in place so that we can end the operation if the Tab
key isn't pressed. We also have to be mindful of the Shift + Tab
combination which we can handle by checking for event.shiftKey
. If the active
element is the first in our focusable
elements array then we know that we can shift the focus to the end and the opposite is true for when we get to the end of our list of elements therefore we can navigate back to the beginning.
trapFocus(event) {
const tab = event.key === "Tab";
if (!tab) {
return;
}
if (event.shiftKey) {
if (document.activeElement === first) {
last.focus();
event.preventDefault();
}
} else {
if (document.activeElement === last) {
first.focus();
event.preventDefault();
}
}
}
The last couple of snippets aren't really anything groundbreaking however this sort of attention to detail and extra bit of effort goes a long way in providing a more inclusive user experience.
Conclusion
Hopefully this has given you an insight into how Crumbs is put together and if you would like to contribute then you can create a pull request by heading over to the Github repository and if you run into any problems then you can raise an issue.
If you have any questions then please don't hesitate to get in touch as we love getting your feedback!
You can also find Crumbs available to download on npm.
Share this article
About the author
Craig E