Kockica — cookie consent by Data Vallis
@datavallis/consent · Drop-in cookie consent for EU sites by Data Vallis. Add one script tag:
Kockica finds the trackers on the page, blocks them until the visitor agrees, builds the banner's categories and the
cookie table for your policy page by itself. GDPR / ePrivacy / ZEKom-2 ready, Google Consent Mode v2 built in.
Built on the MIT-licensed vanilla-cookieconsent (see NOTICE).
Legal notice. Kockica is a technical tool, not legal advice, and using it does not by itself make a website compliant. The site operator, as controller, is responsible for its configuration, texts and policies. The default texts are templates (not reviewed by a lawyer); have them reviewed before commercial use. Provided "as is" under the MIT licence, without warranty.
Installing with an AI agent
Give your coding agent this page's link and the line: "Install Kockica on this site."
Agents: follow AI-INSTALL.md (also in this package:
node_modules/@datavallis/consent/AI-INSTALL.md) — survey, remove the old banner, install, test, report.
Quick start
First thing in <head>, before any other tag:
<script
src="https://cdn.jsdelivr.net/npm/@datavallis/[email protected]/dist/kockica.min.js"
data-site-id="my-site"
data-controller="Example Ltd., Ljubljana"
data-policy-url="/cookie-policy"
data-languages="en,de"
></script>
That is all. Leave your Google Analytics, Meta Pixel, YouTube embeds etc. exactly as they are. On your cookie policy page, add the table:
<div data-kockica-cookie-table></div>
What it does by itself
- Finds known trackers — Google Analytics, Google Ads, Google Tag Manager, Meta Pixel, Hotjar, Microsoft Clarity, LinkedIn Insight, TikTok Pixel, YouTube, Vimeo, Google Maps — in the page's HTML and in tags added later from JavaScript (GTM, pixel snippets).
- Blocks them until consent. Scripts do not run, embeds do not load, no cookies are set. After consent they start; on the next page they start immediately.
- Google tags stay loaded and are kept cookieless by Consent Mode v2 until consent (
googleMode: 'advanced', Google's recommended setup). SetgoogleMode: 'basic'to block them like everything else. - Builds the banner from what it found: only the categories the site really uses (functional, analytics, advertising).
- Cookie tables: the shortcode above renders one table per category (cookie, provider, purpose, duration), and the same tables appear in the banner's settings. Purposes and durations are localised.
- Compliant defaults: opt-in, no pre-ticked boxes, Accept all / Reject all / Settings on the first layer with identical styling (EDPB Cookie Banner Taskforce), controller and purposes named on the first layer (ZEKom-2, art. 225/1).
- Withdrawal as easy as consent: a small single-colour button (Data Vallis cubes) or your own footer link.
- Consent log (optional): one record per choice sent to your endpoint, with no IP or other personal data from the browser.
- 22 languages built in: en, sl, de, hr, it, fr, es, pt, nl, da, pl, cs, sk, hu, bg, uk, ru, el, sq, sv, fi, nb (
no/nnpages get Bokmål). A page loads only its own language (~1.5 KB); add any other language withtranslations.
Limits of automatic blocking
A browser starts loading an <iframe src> that is written in the HTML the moment it reads it, before any script can
react, and its preload scanner may fetch a <script src> early. Kockica aborts the iframe at once and never lets a
blocked script run, so no content loads, nothing renders and no cookie is set — but that very first request may already
have reached the provider. For zero requests before consent, tag such elements yourself (next section). Trackers that
Kockica does not know are not blocked automatically; tag them the same way.
The cookie table shows what Kockica knows about the page it runs on. Tags that appear on only some pages are listed once
a visitor has them in the browser, or when you add them to services.
Not for personalised Google ads
Kockica is not a Google-certified CMP and does not implement IAB TCF. Google requires one for personalised AdSense, Ad Manager and AdMob ads to visitors in the EEA, UK and Switzerland; without it they get only non-personalised or limited ads. When a page loads AdSense or Ad Manager, Kockica says so in the browser console. Google Analytics, Google Ads conversion tracking and remarketing tags work normally through Consent Mode v2.
Tagging elements yourself (optional)
<script type="text/plain" data-category="analytics" src="https://example.com/stats.js"></script>
<iframe data-kockica-category="marketing" data-src="https://www.youtube-nocookie.com/embed/VIDEO_ID" title="Video"></iframe>
Without consent a gated iframe shows a placeholder with an "Allow and show" button.
When Kockica asks again
Kockica remembers which categories each visitor has been shown. When the page later offers a category they have not
seen — marketing adds Meta Pixel through Google Tag Manager, a page gets a YouTube video — the banner appears again:
their earlier choices stay ticked, the new category starts off. Nobody has to bump revision.
With Google Tag Manager on the page, the banner offers all three categories from the start, so "Accept all" already covers tags added to the container later — including tags that GTM itself holds back until a Consent Mode signal is granted, which no script can see in advance.
Not covered: a script Kockica does not recognise, pasted straight into the HTML. Tag it (see above). revision still
asks everyone again, e.g. after you change the texts or your policy.
Changing consent later
A small single-colour button with the Data Vallis cubes sits in the bottom-left corner (revisitPosition: 'right' moves it).
Prefer a link? Set revisitButton: false (CDN: data-revisit-button="false") and add, typically in the footer:
<a href="#" data-kockica-preferences>Cookie settings</a>
The law needs withdrawal to be always available and as easy as consent; a footer link on every page meets that.
npm (React, Next.js, any bundler)
npm i @datavallis/consent
import { initConsent } from '@datavallis/consent';
import '@datavallis/consent/style.css';
initConsent({
siteId: 'my-site',
controller: { name: 'Example Ltd., Ljubljana' },
policyUrl: '/cookie-policy',
languages: ['en', 'de'],
});
Call it once, client-side, as early as possible.
Next.js (App Router)
// app/kockica.tsx
'use client';
import { initConsent } from '@datavallis/consent';
import '@datavallis/consent/style.css';
// One cookie policy page per language. The first entry is the fallback language.
const COOKIE_POLICY: Record<string, string> = {
en: '/en/cookie-policy',
de: '/de/cookie-richtlinie',
};
// Runs when the client bundle loads — before next/script tags (afterInteractive) are added.
if (typeof window !== 'undefined') {
const lang = document.documentElement.lang.slice(0, 2);
initConsent({
siteId: 'my-site',
controller: { name: 'Example Ltd., Ljubljana' },
policyUrl: COOKIE_POLICY[lang] ?? COOKIE_POLICY.en!,
languages: Object.keys(COOKIE_POLICY),
});
}
export default function Kockica() {
return null;
}
// app/[locale]/layout.tsx
import { consentModeDefaultsSnippet } from '@datavallis/consent';
import Kockica from '../kockica';
export default async function Layout({ children, params }: { children: React.ReactNode; params: Promise<{ locale: string }> }) {
const { locale } = await params;
return (
<html lang={locale}>
<head>
{/* Consent Mode defaults before Google tags */}
<script dangerouslySetInnerHTML={{ __html: consentModeDefaultsSnippet() }} />
</head>
<body>
<Kockica />
{children}
</body>
</html>
);
}
On the cookie policy page: <div data-kockica-cookie-table suppressHydrationWarning />. Tables, gated iframes and
tags that appear after a client-side navigation are handled too. The banner keeps the language of the page it was first
shown on until the next full page load.
Options
| Option | Default | |
|---|---|---|
siteId | — (required) | Sent with every log record |
controller.name | — (required) | Shown in the banner and the cookie table |
policyUrl | — (required) | Linked from the banner |
languages | ['sl', 'en'] | Languages your site has; the banner follows <html lang>. First = fallback |
autoDetectLanguage | 'document' | Or 'browser' |
autoBlock | true | Detect and block known trackers |
googleMode | 'advanced' | 'basic' blocks Google tags until consent |
services | detected | Add trackers Kockica cannot see on load: ga4, gtm, googleAds, metaPixel, hotjar, clarity, linkedinInsight, tiktokPixel, youtube, vimeo, googleMaps |
categories | detected | Extra of functional, analytics, marketing |
consentMode | true | Google Consent Mode v2 |
logEndpoint | none | HTTPS URL; logging is off without it |
revision | 0 | Bump to ask everyone again |
reconsentOnNewCategories | true | Ask again when the page offers a category the visitor has not seen |
gtmOffersAllCategories | true | With GTM on the page, offer functional, analytics and advertising |
expiresAfterDays | 182 | 1–395 |
cookieDomain | current host | .example.com shares consent across subdomains |
layout / position | 'box' / 'bottom left' | 'bar' / 'bottom' |
revisitButton | true | Small floating settings button |
revisitPosition | 'left' | 'right' |
branding | true | "Powered by Kockica · Data Vallis" credit in the banner footer (nofollow) |
respectGpc | false | Honour Global Privacy Control |
translations | — | Per-language overrides, merged over built-in texts |
onChange | — | (state, trigger) => void after every choice; trigger is first, change or reconsent |
Script tag: the same options as data-* attributes (data-google-mode="basic", data-auto-block="false", data-services="ga4,vimeo" …).
List only the languages your site really has: the banner speaks the page's language (<html lang>), or the first one
listed if the page's language is not in the list. Languages without a built-in pack (e.g. ja) fall back to English for
any text you do not override. Self-hosting the CDN build? Copy kockica.min.js, style.css and the lang/ folder together.
Consent log contract
POST logEndpoint, body is JSON sent as text/plain;charset=UTF-8 (no CORS preflight):
{
"siteId": "my-site",
"consentId": "8c0e…",
"revision": 0,
"action": "accept_all | reject_all | custom",
"trigger": "first | change | reconsent",
"accepted": ["necessary", "analytics"],
"rejected": ["marketing"],
"language": "en",
"page": "https://example.com/contact",
"timestamp": "2026-11-04T09:00:00.000Z",
"sdkVersion": "0.3.0"
}
The server adds the truncated IP and country. The receiving service is a separate task (see docs/tasks/README.md).
Development
Needs Node 22.18+ (see .nvmrc; nvm use picks it up). The published package itself has no Node requirement — it runs in the browser.
npm ci
npm run check # lint + typecheck + unit tests + build
npx playwright install chromium && npm run test:e2e
npm run demo # http://localhost:4321/demo/ · auto-detection: /demo/auto.html
Publishing: docs/runbooks/publish-npm.md. Project rules: docs/DEVELOPMENT-RULES.md. Current state: docs/HANDOFF.md.
License
MIT © Data Vallis d.o.o. Kockica and the Data Vallis cubes mark are brands of Data Vallis d.o.o. Includes vanilla-cookieconsent © Orest Bida (MIT) — see NOTICE.