Cookie banners are among the most frequently misimplemented UI elements on the German web. Almost every second site I audit violates the TDDDG (Telecommunications-Digital-Services Data Protection Act) — usually without the operator knowing. This article explains what the law requires, the most common mistakes I see, and how a GDPR-compliant banner is built technically.
The basics: § 25 TDDDG
Since 14 May 2024 the law previously known as TTDSG is called TDDDG. The key paragraph stayed the same: § 25 TDDDG requires active consent from the user before information is stored in their end device — whether classic cookies, Local Storage, Session Storage or fingerprinting techniques. The only exception is strictly technically necessary storage like session cookies for login or a CSRF token.
The legal basis for the TDDDG is the EU ePrivacy Directive, complemented by the GDPR. On top there are two important rulings that are often overlooked in practice:
- CJEU "Planet49" (C-673/17, 2019): pre-checked boxes are not valid consent. The user has to click actively.
- German DSK ruling of 22 August 2024: rejecting must be possible with the same effort and at least the same prominence as accepting. If "Accept" is designed as an eye-catching primary button and "Reject" is hidden in a grey secondary, the consent is not freely given — and therefore invalid.
The five most common violations
1. UX inequality between Accept and Reject
This is the clearest and most documented violation. Classic case: "Accept all" glows in brand colour, "Reject" is a discreet grey text link. Fix: both buttons equally weighted, same background, same hover. No dark patterns.
// Bad — dark pattern
<button className="bg-orange-500 ...">Accept all</button>
<button className="bg-slate-700 ...">Necessary only</button>
// Good — UX-equal, DSK-compliant
<button className="bg-orange-500 ...">Necessary only</button>
<button className="bg-orange-500 ...">Accept all</button>
2. Banner loads trackers before the user decides
If the CMP asks for consent but Google Analytics, Facebook Pixel or embedded YouTube frames are already active on first page load, that is a serious violation. I check this in every audit with Playwright: open the browser fresh, load the page, evaluate cookies and third-party requests before any interaction. Tracker URLs in there? → violation.
3. No granularity
You need at least three categories: Necessary, Statistics, Marketing. Anyone bundling everything into a single "Accept" button doesn't have valid consent — the user can't decide what to allow.
4. No withdrawal
Consent must be revocable at any time with the same effort it was granted. A cookie banner that never appears again after acceptance and offers no "change cookie settings" link in footer or privacy policy is not compliant.
5. No re-consent on changes
When new third parties are added or existing ones change their purposes, consent has to be requested again. Consent given two years ago for "marketing" doesn't cover a freshly added TikTok pixel.
How a compliant banner looks technically
A clean cookie banner in Next.js with a Server-Components-compatible pattern sets nothing before consent. Only after an active click are the selected scripts loaded:
"use client";
const COOKIE_CONSENT_KEY = "consent";
const COOKIE_PREFERENCES_KEY = "consent_prefs";
export function CookieBanner() {
const [visible, setVisible] = useState(false);
const [prefs, setPrefs] = useState({
necessary: true, // always active
statistics: false,
marketing: false,
});
useEffect(() => {
if (!localStorage.getItem(COOKIE_CONSENT_KEY)) {
setVisible(true);
}
}, []);
const save = (p: typeof prefs) => {
localStorage.setItem(COOKIE_CONSENT_KEY, new Date().toISOString());
localStorage.setItem(COOKIE_PREFERENCES_KEY, JSON.stringify(p));
setVisible(false);
window.dispatchEvent(new CustomEvent("consentChanged", { detail: p }));
};
// Other components listen for "consentChanged" and load their scripts
// only afterwards.
}
Important: statistics/marketing scripts are not embedded directly in the layout but loaded via a listener reacting to the `consentChanged` event. Before the event nothing is active — verified by Playwright audit.
Audit trail: what to store, what not
Supervisory authorities can ask for proof that user X consented on day Y to a specific configuration. A pure localStorage entry is not enough — that's not provable. I log consents server-side in a dedicated table:
- Hash of the preferences (e.g. SHA-256 over the settings JSON)
- Timestamp
- User agent (truncated, no IP — that's personal data)
- Optional session ID or anonymous visitor ID
Retention: 3 years after last consent — then delete. The period derives from civil-law statute of limitations on damage claims.
What I do concretely
In a compliance audit I systematically check all the above with Playwright and a manual walk-through. Typical procedure: first reject all preferences, then observe what cookies, LocalStorage entries and third-party requests still appear. Then the same with "Accept all". The delta is where the banner delivers what it promises — or doesn't.
On the implementation side I take care of selecting and integrating a GDPR-compliant CMP (self-hosted or EU provider, no US platform), a Server-Components-compatible pattern in Next.js, consent logging in Supabase EU, testing the reject path with a real browser, updating the privacy policy with current providers, and a re-consent workflow when the cookie list changes.
More details and an audit offer for your project at /compliance/cookie-consent.



