Ask for Consent
Collect user consent for Google Ads conversion tracking with minimal friction. The consent drawer appears only when users attempt conversion actions, preserving the gclid parameter for proper attribution.
Overview
Two components work together for flexible consent management:
- ConsentDrawer - The UI drawer that displays consent options
- AskForConsentOnInternalNavigation - Automatic consent on internal link clicks
Both integrate with @dynamic-type/analytics for GDPR-compliant consent collection:
- Blocks conversion actions until consent is given
- Preserves gclid/wbraid from URL during consent flow
- Stores consent permanently in cookies (365 days)
- Never asks twice - remembers user’s decision
- Minimal friction - only shown when needed
Usage Pattern 1: Explicit CTA Consent
Mark specific buttons/forms that require consent.
Example: Button Click Requiring Consent
---
import { ConsentDrawer } from "@dynamic-type/ddi";
---
<!-- Add ConsentDrawer once per page -->
<ConsentDrawer privacyPolicyUrl="/integritetspolicy" />
<!-- Any button requiring consent -->
<button data-ddi-requires-consent data-ddi-event="book-appointment">
Boka tid
</button>How it works:
- User clicks button
- If consent not given: Drawer opens, button action blocked
- User accepts/rejects in drawer
- Original button action resumes
Usage Pattern 2: Automatic Internal Navigation
Ask for consent on ALL internal link clicks automatically.
Example: Intercept All Internal Links
---
import { ConsentDrawer, AskForConsentOnInternalNavigation } from "@dynamic-type/ddi";
---
<ConsentDrawer privacyPolicyUrl="/integritetspolicy" />
<AskForConsentOnInternalNavigation />
<!-- These links will automatically trigger consent if not given -->
<a href="/behandlingar">Se alla behandlingar</a>
<a href="/kontakt">Kontakta oss</a>
<a href="/boka">Boka tid</a>
<!-- External links are NOT affected -->
<a href="https://external.com">External site</a>How it works:
- All internal link clicks are intercepted automatically
- Consent drawer appears: “Innan du går vidare till nästa sida…”
- After consent: Navigation continues
- External links are not affected
- Based on same pattern as EventObserver (delegated event handling)
Example 3: Form Submission
Collect consent before form data is sent:
<ConsentDrawer privacyPolicyUrl="/integritetspolicy" />
<form action="/submit-contact" method="POST" data-ddi-requires-consent>
<input type="email" name="email" required />
<button type="submit">Skicka</button>
</form>Behavior:
- Submit event intercepted
- Consent drawer shown
- After consent: Form submission proceeds
- Can track form conversion in Google Ads
Example 4: Combining Both Patterns
Use both explicit CTA consent AND automatic internal navigation consent:
---
import { ConsentDrawer, AskForConsentOnInternalNavigation } from "@dynamic-type/ddi";
---
<ConsentDrawer privacyPolicyUrl="/privacy" />
<AskForConsentOnInternalNavigation />
<!-- Explicit CTA consent -->
<button data-ddi-requires-consent>Boka nu</button>
<!-- Internal links automatically require consent -->
<a href="/behandlingar">Se behandlingar</a>
<a href="/kontakt">Kontakta oss</a>
<!-- Forms can also require consent -->
<form data-ddi-requires-consent>...</form>Behavior:
- All CTAs marked with
data-ddi-requires-consenttrigger consent - All internal link clicks trigger consent (via AskForConsentOnInternalNavigation)
- Consent is shared - only asked once
- User can navigate freely after consenting
Components
ConsentDrawer
The drawer UI that displays consent options. Required for both usage patterns.
Props:
privacyPolicyUrl(required): Link to your privacy policyvariant(optional): ‘normal’ | ‘inverted’ | ‘brand’ - Visual themeacceptButtonText(optional): Default: “Godkänn”rejectButtonText(optional): Default: “Neka”
Note: The consent text is hardcoded in Swedish for GDPR compliance and cannot be customized.
AskForConsentOnInternalNavigation
Automatically intercepts ALL internal link clicks and asks for consent if not given.
Props: None
Usage:
<AskForConsentOnInternalNavigation />This component uses delegated event handling (similar to EventObserver) to intercept all internal link clicks on the page. It detects internal vs external links the same way as track-event-handler.ts.
Attributes for Tracked Elements
Add to buttons or forms that should explicitly trigger consent:
data-ddi-requires-consent: Marks element as requiring consentdata-ddi-event(optional): Event name for analytics (e.g., “book”, “contact-form”)
Note:
- Combine
data-ddi-requires-consentwithdata-ddi-eventto track which CTA triggered the consent - Links do NOT need these attributes when using
AskForConsentOnInternalNavigation- it handles all internal links automatically - The backend determines which events are conversion events
Swedish Text Content
Default drawer text adapts to context:
For explicit CTAs (buttons, forms):
Innan du går vidare
För att utvärdera och förbättra vår marknadsföring använder vi en cookie som kopplar ditt besök till annonsen som ledde dig hit.
Informationen är anonymiserad och delas endast med Google Ads för konverteringsmätning.
Genom att godkänna hjälper du oss förbättra tjänsten.
Läs mer i vår integritetspolicy
[Godkänn] [Neka]
For internal navigation:
Innan du går vidare till nästa sida
För att utvärdera och förbättra vår marknadsföring använder vi en cookie som kopplar ditt besök till annonsen som ledde dig hit.
Informationen är anonymiserad och delas endast med Google Ads för konverteringsmätning.
Genom att godkänna hjälper du oss förbättra tjänsten.
Läs mer i vår integritetspolicy
[Godkänn] [Neka]
How It Works Internally
- Page Load: ConsentDrawer checks
currentConsent()from analytics - User Clicks CTA/Link: Click event intercepted
- Consent Check:
- If already answered: Proceed immediately
- If not answered: Block action, show drawer
- User Decides: Clicks “Godkänn” or “Neka”
- Server Call:
setConsent({ trackConversions: true/false })sent to/set-consent - Cookies Set:
dt-consent(readable) - User’s decisiondt-tracking(HttpOnly) - UUIDdt-gclid(HttpOnly, 90 days) - If consent givendt-wbraid(HttpOnly, 90 days) - If consent given
- Resume Action: Original click handler executes
- Future Visits: Consent remembered, no drawer shown
Integration with Analytics
The consent components work seamlessly with @dynamic-type/analytics:
import { currentConsent } from "@dynamic-type/analytics/client";
// Check current consent status
const consent = currentConsent();
// Returns: undefined | { trackConversions: boolean }
// Consent is automatically sent to server
// No manual integration neededGDPR Compliance
✅ Prior consent obtained - Before any tracking cookies ✅ Freely given - User can reject and still use site ✅ Specific & informed - Clear purpose stated ✅ Unambiguous - Explicit button click required ✅ Revocable - User can change decision (future feature) ✅ Equal prominence - Accept/reject buttons equal size ✅ No dark patterns - Swedish IMY compliant
Example 5: Consent Status Component
Display current consent status and allow users to change it (GDPR Article 7.3 - withdrawal must be as easy as giving consent):
---
import { ConsentStatus, ConsentDrawer } from "@dynamic-type/ddi";
---
<ConsentDrawer privacyPolicyUrl="/integritetspolicy" />
<!-- Typically placed in footer -->
<footer>
<ConsentStatus />
</footer>Component shows:
- “Annonsspårning: Inget val gjort” - When no consent decision made
- “Godkänner annonsspårning” - When user has consented
- “Nekar annonsspårning” - When user has rejected
Props:
changeButtonText(optional): Text for change button. Default: “Ändra”drawerId(optional): ID of the ConsentDrawer to control. Default: “consent-drawer”
Accessibility:
- Status updates are announced to screen readers (aria-live=“polite”)
- Change button has clear aria-label
- Keyboard accessible
Live Examples
Try clicking the buttons below to see the consent flow: