Public web component documentation for consent-dialog, consent-guard, consent-missing, and consent-validator.
Main entry component for the PrivacyKit consent UI and orchestration logic.
| Attribute | Type | Default / Required | Description |
|---|---|---|---|
| variant | standard | panel | modern | modest | standard | Visual variant of the dialog. |
| theme | standard | dark | teal | slate | light | vibrant | high-contrast | standard | Visual theme of the dialog. |
| expires-days | number | 180 | Cookie lifetime in days. |
| version | number | 0 | Consent schema version used for re-consent decisions. |
| google-consent-mode | boolean | unset | Emits Google Consent Mode v2 consent signals based on the visitor's PrivacyKit consent choices. |
| locale | da | de | en | es | fi | fr | it | nl | no | pl | sv | browser-derived | Locale override. |
| hide-summary-part-2 | boolean | unset | Hides the dialog summary part 2 (dialog-summary-part-2). |
| hide-necessary | boolean | unset | Hides the necessary category. |
| hide-preferences | boolean | unset | Hides the preferences category. |
| hide-analytics | boolean | unset | Hides the analytics category. |
| hide-marketing | boolean | unset | Hides the marketing category. |
| hide-readmore | boolean | unset | Hides the expandable Read More section. |
| hide-privacykit-badge | boolean | unset | Hides the PrivacyKit badge shown at the bottom of the consent dialog body. |
| hide-privacy-policy-link | boolean | unset | Hides the default Privacy Policy link entirely. Use `privacy-policy-url` instead to keep the link visible and redirect users to your own policy page. |
| privacy-policy-url | string | unset | URL to redirect users to when they click the Privacy Policy link. When set, the link opens this URL instead of the built-in privacy policy dialog. |
| demo | boolean | unset | Demo mode: disables auto-open and limits certain features. Not GDPR compliant and must not be used in production. |
| dismissible | boolean | false | Controls whether dialog can be dismissed via backdrop/escape if no accept/reject consent has been given. |
| show-fab | boolean | false | Enables the consent preferences button. Has no visible effect until a consent cookie exists. |
| fab-position | left | right | left | Pins the FAB to the bottom-left or bottom-right corner of the viewport. |
| dialog-position | left | right | left | Controls which side of the screen the dialog appears on. Only applies to the modern and modest variants — standard and panel are always centered. |
Example
<consent-dialog theme="dark" variant="dashboard" expires-days="90"
version="1" locale="en" hide-marketing hide-privacy-policy-link dismissible>
</consent-dialog>Design tokens expose a stable set of CSS variables that let you customize appearance—colors, spacing, typography, and more—without touching the component's internal implementation. Built-in themes are powered by the same token system, allowing you to override individual styles or create a completely custom appearance.
| Design Tokens | Description |
|---|---|
| --pk-transparency | Transparency of the dialog surface and its nested cards/accordions, as a percentage. 0% (default) is fully solid; clamped to a maximum of 50%. |
| --pk-bg-color | Surface background for the entire dialog shell. Mixed with --pk-transparency to produce the final rendered color. |
| --pk-paper-color | Paper surface for cards, accordions, and panels inside the dialog. Also mixed with --pk-transparency. |
| --pk-text-color | Base text color applied to headings and body text. |
| --pk-text-color-on-primary | Text color used on elements filled with the primary color, such as solid buttons. |
| --pk-primary-color | Primary accent used for CTAs, focus rings, and links. |
| --pk-secondary-color | Secondary accent, mainly used for the switch/thumb UI state. |
| --pk-focus-ring-color | Focus outline color used for keyboard focus states. |
| --pk-font-family | Font stack for all textual elements; falls back to your body font token. |
| --pk-spacing-unit | Spacing scale unit that drives paddings/gaps between sections. |
| --pk-control-border-color | Border color for controls inside the dialog — cards, accordions, buttons, and similar elements. |
| --pk-control-border-width | Border width for controls inside the dialog — cards, accordions, buttons, and similar elements. |
| --pk-control-border-radius | Corner radius for buttons, inputs, and interactive controls. |
| --pk-dialog-border-color | Border color for the dialog's own chrome — the outer panel outline and the header/footer divider strokes. |
| --pk-dialog-border-width | Border width for the dialog's own chrome — the outer panel outline and the header/footer divider strokes. |
| --pk-dialog-border-radius | Corner radius for the outer consent dialog container. |
| --pk-dialog-max-height | Maximum height of the consent dialog; when content exceeds this limit, the dialog body scrolls. |
| --pk-dialog-shadow | Box shadow applied to the consent dialog overlay. |
Example
<consent-dialog theme="light" style="
--pk-bg-color: #faf7f2;
--pk-paper-color: #f7eede;
--pk-primary-color: #b08968;
--pk-font-family: 'Segoe UI', Tahoma, sans-serif;
--pk-control-border-radius: 10px;
--pk-dialog-border-radius: 20px;
--pk-dialog-max-height: 50vh;
--pk-dialog-shadow: 10px 20px rgba(0, 0, 0, 0.5);
">
</consent-dialog>Inject custom content into specific parts of a component using named HTML slots — giving you full control over content and styling without modifying the component itself.
| Slot Name | Description |
|---|---|
| dialog-logo-top | Optional logo or brand image displayed in the header of the consent dialog. |
| dialog-logo-right | Optional logo or brand image displayed at the top right of the consent dialog. |
| dialog-title | Overrides the title in the dialog header. |
| dialog-summary-part-1 | Injects custom text for the first intro paragraph below the dialog title. |
| dialog-summary-part-2 | Injects custom text for the second intro paragraph that explains consent choices. |
| necessary-content | Replaces the default description for the Necessary category. |
| preferences-content | Custom text inside the Preferences category accordion body. |
| analytics-content | Custom text inside the Analytics category accordion body. |
| marketing-content | Custom text inside the Marketing category accordion body. |
| read-more-title | Sets the heading for the expandable “Read more” section at the bottom. |
| read-more-content | Supplies the body content shown when the “Read more” section expands. |
| privacy-policy-content | Replaces the default Privacy Policy copy. Use wording vetted by your own legal counsel, because PrivacyKit can’t verify GDPR compliance once you customize it. |
Example
<consent-dialog theme="standard" variant="standard" locale="en" version="1">
<img slot="dialog-logo-top" width="100px" src="/logo.png" alt="Company logo" />
<div slot="dialog-title" class="your-class">
<h2>We use cookies</h2>
</div>
<span slot="marketing-content">
<b>We currently do not collect cookies for marketing purposes.</b>
</span>
</consent-dialog>
PrivacyKit automatically applies an appropriate privacy profile based on the visitor's detected location. No additional configuration is required.
Visitors located in Europe are presented with the GDPR/ePrivacy consent model. Analytics, marketing, and other non-essential technologies remain blocked until the visitor has granted consent.
Visitors outside Europe are presented with the CPRA privacy model. Consent is granted automatically, allowing the website to operate without an initial consent dialog. Visitors can open the PrivacyKit dialog at any time to review or withdraw their privacy choices. If consent is withdrawn, PrivacyKit requests a page refresh to ensure that blocked technologies are no longer executed.
PrivacyKit automatically determines whether the visitor is located within Europe. No configuration is required.
<consent-dialog> ships with built-in translations for English, Norwegian, German, Polish, Spanish, French, Italian, Dutch, Swedish, Danish, and Finnish — no translation files to load or maintain yourself.
By default, the dialog reads the visitor's browser language and displays the matching translation automatically. If the browser is set to a language PrivacyKit doesn't support, it falls back to English.
To force a specific language regardless of the visitor's browser settings, set the locale attribute — see the table above for the full list of supported codes.
<consent-dialog locale="en"></consent-dialog>When google-consent-mode is enabled, PrivacyKit reads the visitor's consent choices and signals them to Google Tag Manager or gtag using the Google Consent Mode v2 protocol. This happens automatically on page load and whenever the visitor changes their consent. No additional configuration is required beyond adding the attribute.
PrivacyKit maps its three consent categories to Google's seven Consent Mode v2 fields:
| GCM v2 field | Mapped from | Value |
|---|---|---|
| analytics_storage | Analytics | granted / denied |
| ad_storage | Marketing | granted / denied |
| ad_user_data | Marketing | granted / denied |
| ad_personalization | Marketing | granted / denied |
| functionality_storage | Preferences | granted / denied |
| personalization_storage | Preferences | granted / denied |
| security_storage | — | Always granted |
security_storage is always granted as it covers security-essential browser storage and is not subject to optional tracking consent.
On page load, PrivacyKit makes sure window.dataLayer and a gtag function exist, using Google's standard snippet and keeping any that are already on the page. It queues a default with everything denied, then the visitor's current consent, and queues an update on every consent change:
gtag('consent', 'update', {
analytics_storage: 'granted' | 'denied',
ad_storage: 'granted' | 'denied',
ad_user_data: 'granted' | 'denied',
ad_personalization: 'granted' | 'denied',
functionality_storage: 'granted' | 'denied',
personalization_storage: 'granted' | 'denied',
security_storage: 'granted'
});The dataLayer is an in-memory queue on the page; nothing is stored. Google Tag Manager and gtag.js process the queued commands in order when they load, so the consent state is applied before any Google tag runs, even when Google loads later.
If your page has its own Google snippet, keep an existing dataLayer (window.dataLayer = window.dataLayer || [], as Google's snippets do).
Debugging tip: PrivacyKit logs its Google Consent Mode v2 activity to the browser console, so you can verify the behaviour directly in DevTools:
[PrivacyKit] Google Consent Mode v2 enabled.
Shown once on page load when google-consent-mode is set. The denied default is queued at the same time.
[PrivacyKit] Google Consent Mode v2 update queued.
A consent update was queued in the dataLayer, on page load and on every consent change. Google applies it when its tags load.
openConsentDialog(): void
onConsentDialogClosed(callback: () => void): () => void
openPrivacyPolicyDialog(): voidConditionally renders children once the provided consent expression evaluates to true.
| Attribute | Type | Default / Required | Description |
|---|---|---|---|
| consent | string | unset | Consent expression to evaluate before rendering children. When unset, all categories are required. |
| source | string | unset | Where consent is read from. When unset, the guard uses consent from the PrivacyKit consent dialog. "google-consent-mode" reads the consent collected by your existing consent banner through Google Consent Mode v2. Unknown values keep the guard blocked. |
| Expression | Consent Required | Description |
|---|---|---|
| All Categories | If the consent attribute is omitted, all categories must be accepted for the guard to activate. | |
| necessary | None | Prevents false positives in Consent Validator for necessary resources that are not automatically recognized by PrivacyKit. |
| preferences | Preferences | |
| analytics | Analytics | |
| marketing | Marketing | |
| preferences+analytics | Preferences AND Analytics | For scripts that need both kinds of consent, e.g. HubSpot tracking. |
| preferences|analytics | Preferences OR Analytics | For containers that check consent per tag, e.g. Google Tag Manager. |
| preferences+marketing | Preferences AND Marketing | For scripts that need both kinds of consent, e.g. HubSpot tracking. |
| preferences|marketing | Preferences OR Marketing | For containers that check consent per tag, e.g. Google Tag Manager. |
| analytics+marketing | Analytics AND Marketing | For scripts that need both kinds of consent, e.g. HubSpot tracking. |
| analytics|marketing | Analytics OR Marketing | For containers that check consent per tag, e.g. Google Tag Manager. |
Example 1 – Manage Scripts
<consent-guard consent="analytics">
<script type="text/plain" data-src="https://static.hotjar.com/c/hotjar-XXXXXXX.js?sv=6" data-async></script>
</consent-guard>
Example 2 – Manage Embedded Content
<consent-guard consent="analytics+marketing">
<iframe
title="YouTube video"
data-src="https://www.youtube.com/embed/abc123"
width="560"
height="315"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</consent-guard>
Important: Notice that managed resources in the examples use data-src instead of src, and type="text/plain" is used for scripts. PrivacyKit activates managed content after consent is granted — otherwise resources may load immediately and appear as hardcoded in Consent Validator.
Guards can enforce the consent collected by your existing banner. Add source="google-consent-mode" to <consent-guard>, and the guard reads the consent from your banner instead of the PrivacyKit consent dialog. Scripts and embeds inside the guard only load once the visitor has agreed.
window.dataLayer, on every page load and whenever the choice changes.Use the consent from your existing banner
<!-- Your existing consent banner, with Google Consent Mode v2 enabled. -->
<script src="https://cdn.your-consent-banner.com/banner.js"></script>
<!-- PrivacyKit -->
<script type="module" src="https://cdn.privacykit.eu/v1/privacykit.esm.js"></script>
<!-- Hotjar loads only after the visitor accepts analytics in your banner -->
<consent-guard consent="analytics" source="google-consent-mode">
<script type="text/plain" data-src="https://static.hotjar.com/c/hotjar-XXXXXXX.js?sv=6" data-async></script>
</consent-guard>
<!-- Embedded content that requires marketing consent -->
<consent-guard consent="marketing" source="google-consent-mode">
<iframe title="YouTube video" data-src="https://www.youtube.com/embed/abc123" width="560" height="315"></iframe>
</consent-guard>
You can use PrivacyKit with Google Tag Manager in two ways: strict, where Tag Manager waits for consent, or lenient, where Tag Manager loads right away and its own consent settings decide which tags fire. In both cases, add google-consent-mode to <consent-dialog>, so the tags inside Tag Manager know what the visitor chose.
Nothing from Tag Manager loads, and nothing is sent to Google, until the visitor has consented. A German court (VG Hannover, 2025) has ruled that loading Google Tag Manager requires consent, and strict mode follows that reading. Set the guard's consent to match what the container fires: analytics or marketing if it fires one kind of tag, or analytics|marketing if it fires tags of both kinds, so it loads as soon as the visitor accepts either. From there, Tag Manager handles consent for each tag.
<consent-dialog google-consent-mode></consent-dialog>
<consent-guard consent="analytics|marketing">
<script type="text/plain" data-src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX" data-async></script>
</consent-guard>
Load Tag Manager as usual. Google's tags follow the Consent Mode signals from the dialog. Other tags in the container, such as Meta or LinkedIn, need consent settings in Tag Manager. Consent Validator shows Tag Manager as not guarded. That's by design: the Validator reports everything that isn't inside a guard.
Lenient is also the setup for websites that use Consent Mode Advanced on purpose. With consent denied, Google's tags still send data without cookies, such as the page address and device details, so Google can model conversions. PrivacyKit supports this setup. Whether it's appropriate for your website is a decision for you and your legal adviser.
Render fallback content whenever an associated consent-guard blocks the primary experience.
| Attribute | Type | Default / Required | Description |
|---|---|---|---|
| for | string | required | Id of the related <consent-guard> element. |
Example
<consent-guard id="analytics-guard" consent="analytics">
<script type="text/plain" data-src="https://example.com/analytics.js"></script>
</consent-guard>
<consent-missing for="analytics-guard">
Please accept analytics cookies to continue.
</consent-missing>
Consent Validator observes outgoing requests and validates consent guard coverage on your live website, surfacing unmanaged trackers and regressions introduced by website changes. Inactive by default, it remains invisible to visitors and is safe to ship in production.
| Attribute | Type | Default / Required | Description |
|---|---|---|---|
| debug | boolean | false | Enables the Consent Validator panel on page load. For development environments only. |
| delay | number | 5000 | Network observation window (in milliseconds) before the Consent Validator begins validating endpoint usage and consent guard coverage. |
| ignore-first-party-subdomains | boolean | true | When true, requests to subdomains of the current domain are silently ignored. |
| fab-position | left | right | right | Controls which side of the viewport the Consent Validator FAB is pinned to. |
Example
<consent-validator debug delay="5000" ignore-first-party-subdomains="true" fab-position="left"></consent-validator>
Consent Validator remains hidden from visitors even when included in the production bundle. The recommended way to activate it on a live website is to append ?privacykit=validate to the URL — it activates the validator only for that browser session, leaving the visitor experience unaffected.
Toggle programmatically:
window.PrivacyKit?.toggleConsentValidator();
Avoid flickering before the web components finish loading. Without this snippet the slotted or guarded HTML can flash briefly when:
consent-dialog can flash briefly if slotted elements are used and if they render before the component definition loads.consent-guard can flash briefly if it's used for conditional HTML and it renders before the component activates.consent-missing can flash briefly if consent-missing is used and it renders before the component activates.Add styling in your light DOM to avoid flickering.
consent-dialog:not(:defined) [slot] {
display: none;
}
consent-guard:not([active]) {
display: none;
}
consent-missing:not([active]) {
display: none;
}