How it works
You do not need this page to install Consentio. You need it when something is not behaving and you want to know what should have happened.
⏱️ The sequence, in your HTML
consentio-loader.min.js is small and blocking, and it does its most important work before it downloads
anything at all.
- It finds its own tag by the
data-consentio-loaderattribute. No tag, no run — it printsscript not foundand stops. - It reads the consent cookie and works out the seven Google signals from it. If there is no cookie, it uses the built-in fallback rather than “everything denied” — the difference matters, and the cookie page explains why.
- It pushes
consent defaultonto thedataLayer, synchronously. This is why the tag blocks. Nothing has been fetched yet; it does not need anything to have been. - It publishes
window.ConsentioDefault— what that push was built from. - Everything after this point is asynchronous and the page carries on: it injects
consentio.min.js, fetches your settings, language and cookie files, builds the banner, and inserts it. A language file that does not load is replaced by the built-in English and a cookie table that does not load by an empty one, each with one warning on the console; a settings file that does not load stops the banner, with an error. - Once there is a stored answer — one just given, or one from an earlier visit — the banner releases
every script marked
type="text/plain" data-consentiowhose category is granted. Hold a script until consent has what that reaches and what it does not.
Steps 1 to 4 are why async and defer break it. Both let the browser run the tag after your tag manager
has already read consent, and there is no way to correct that afterwards — a tag manager reads consent once,
at load.
🏷️ The sequence, in Tag Manager
- The Consent Initialization - All Pages trigger fires. Tag Manager guarantees this runs before every other trigger in the container.
- If
window.ConsentioDefaultis already set, the template stands down — the script tag from the other route ran on this page — and prints one line saying so. - The template reads the same cookie, by the same rules, in sandboxed template code, and sets the consent default through Tag Manager’s own consent API.
- With Language on A published language pack, it calls
injectScriptfordist/i18n/<locale>.jsat the same pinned version and reads the pack offwindow.ConsentioLanguage. A pack that does not load is logged and the banner keeps its built-in English. - It calls
injectScriptforconsentio.min.jsat a pinned version, thenConsentio.Createwith the three inputs its pickers hold and the pack, if there was one.
injectScript is always asynchronous, which is why step 3 cannot be handed to the file it loads — by the
time that file runs, Tag Manager has already decided what it may do.
The template’s entry point into the bundle:
// Three objects, one per concern - the same three the JSON files hold on the other route.
Consentio.Create(settings, language, cookies);
Create puts the instance on window.ConsentioInstance itself, so there is nothing to assign afterwards.
That is what the template’s run-once guard reads, and what your own page calls to
reopen the settings.
The cookie is a contract, not an implementation detail. The template reads it a second time, in a
different language, in Tag Manager’s sandbox. Its name, its version field, its shape and what “no stored
answer” means all have to agree exactly, or the two routes disagree about the same visitor — silently.
Anyone writing a reader implements against
the cookie page, not against a guess, and
the fallback rule is the part
that is easiest to get wrong.
🧩 The two routes, in technical terms
| Directly in the site | Google Tag Manager custom template | |
|---|---|---|
| What you add | consentio-loader.min.js as a plain blocking <script> in <head>, above the tag manager snippet |
the Consentio tag, on the Consent Initialization - All Pages trigger |
| What pushes the consent default | the loader, on its first pass, before it fetches or injects anything | the template’s own sandboxed code, before it calls injectScript |
| Where settings come from | three JSON files, fetched by URL | the same three files, each in a variable or on the page |
| Where a published language pack comes from | your own copy at data-language-url, or the CDN at the loader’s version with data-language |
the CDN at the template’s version, when Language asks for it |
| Uses the loader | yes | no — never |
| What it holds back | scripts marked type="text/plain" data-consentio, released when their category is granted |
the same marked scripts — the banner does the releasing on both routes — and tags in the container, through its own consent settings |
| The cost | it blocks. Around 5.4 KB has to download before the page paints | the container covers only what it loads; a script pasted into the page still has to be marked |
Choose a route has the same comparison in the terms a site owner decides by.
🔒 The banner is inside a closed shadow root
The bar, the settings panel and the overlay are all rendered inside a shadow root attached with
{ mode: 'closed' }. Two consequences worth knowing:
- Your CSS cannot reach it, and its CSS cannot reach your page. There is nothing to override and nothing to accidentally break. Restyling means changing the source, not writing a selector.
document.activeElementreturns the host element, not whatever is focused inside. A closed root retargets it. If you are writing anything that inspects focus around the banner, that is the trap.
⚖️ What it weighs
Measured on the files attached to the 0.3.0 release.
| File | Minified | Compressed | When it loads |
|---|---|---|---|
consentio-loader.min.js |
5.4 KB (5,515 bytes) | 2.4 KB | blocking, in <head>, before the page paints |
consentio.min.js |
42.5 KB (43,566 bytes) | 13.1 KB | in the background, after the default is already pushed |
The compressed column is gzip, which is what almost any server will do for you. Only the first file is on the critical path, and only because the answer has to reach your tag manager before it decides anything. On the Tag Manager route neither file blocks.
⌨️ Keyboard and screen readers
- The bar is a region named by
barTitle. WithconsentRequiredon it is a dialog instead, and Tab stays inside it until the visitor answers. - The settings panel is a dialog named by
modalTitle. Tab stays inside it while it is open, and Escape closes it the way Cancel does. - Each category is a heading the visitor can expand with Enter or Space to read its description and
its cookie table. The switch is a checkbox named after the category; on
strictly_necessaryit is disabled and showsalwaysOnLabel. - The round reopen button is named
buttonSettings, in whichever language the pack gives it. - After an answer focus moves to the round button, or back to wherever it was on the page when the button is hidden. Cancelling before answering puts it back on the bar.
🔍 What ends up on window
The three the loader leaves, listed on the loader tag.
window.ConsentioDefault is the useful one when you are checking behaviour: it is set by the loader and by
nothing else, so its absence tells you a page is running the Tag Manager route.