Cookie-Banner DSGVO-konform bauen — ohne Dark Patterns
Welche Anforderungen DSGVO Art. 7 und TTDSG §25 wirklich stellen, warum die meisten Banner gegen mindestens eine davon verstoßen, und ein minimales Pattern, das beide Pflichten erfüllt.
von Seitenbefund Werkstatt
Ein DSGVO-konformes Cookie-Banner ist kein Designproblem. Es ist eine
Anwendung der drei Anforderungen aus DSGVO Art. 7 ("Bedingungen für
die Einwilligung") und TTDSG §25 (Endgeräte-Speicherung): freiwillig,
informiert, jederzeit widerrufbar. Wenn Ihr Banner gegen eine davon
verstößt, ist die gespeicherte Einwilligung unwirksam — auch wenn der
Nutzer "Akzeptieren" geklickt hat.
In Audits sehe ich immer wieder dieselben drei Verstöße:
"Alle akzeptieren" prominent, "Ablehnen" versteckt. Verstößt
gegen die Freiwilligkeit. Der EuGH hat in Planet49 (C-673/17)
geklärt, dass die Wahlmöglichkeit gleichwertig sein muss. Ein
Link in der Fußzeile reicht nicht.
Ein Klick auf das Schließen-Kreuz wird als Einwilligung
gewertet. Das ist der Klassiker: kein aktives Zustimmen, also
keine Einwilligung im Sinne der DSGVO.
Cookies werden gesetzt, bevor der Nutzer wählt. Tracking-
Skripte laden, der Banner kommt nach. TTDSG §25 erlaubt
Speicherung ohne Einwilligung nur, wenn sie "unbedingt
erforderlich" ist — Analytics ist es nicht.
Art. 7 Abs. 2 DSGVO: Die Anfrage muss in verständlicher und
leicht zugänglicher Form, in einer klaren und einfachen Sprache
vorgelegt werden.
Art. 7 Abs. 3 DSGVO: Der Widerruf muss so einfach sein wie das
Erteilen. Wenn das "Akzeptieren" ein Klick ist, ist das Widerrufen
ein Klick.
TTDSG §25 Abs. 1: Speichern und Auslesen auf dem Endgerät
benötigt vorherige Einwilligung — außer bei "unbedingt
erforderlichen" Cookies (Session, CSRF, Sprachauswahl).
Drei Schema-Typen, die kleine und mittelständische Websites spürbar in Rich Results bringen — und sechs, die zwar gut klingen, aber im Audit nichts bewegen.
Was die drei Kennzahlen wirklich messen, warum sie sich gegenseitig beeinflussen, und welche drei Eingriffe in den meisten Audits den größten Hebel haben.
Das Banner braucht drei Buttons mit gleicher visueller Gewichtung:
"Alle akzeptieren", "Nur notwendige", "Auswahl anpassen". Die
Datenverarbeitung startet erst nach Klick.
type Choice = 'all' | 'essential' | 'custom'
export function CookieBanner() {
const [open, setOpen] = useState(true)
const decide = (choice: Choice) => {
setConsentCookie(choice)
if (choice === 'all') loadAnalytics()
setOpen(false)
}
if (!open) return null
return (
<div role="dialog" aria-labelledby="cookie-h">
<h2 id="cookie-h">Cookie-Einstellungen</h2>
<p>Wir nutzen Cookies für statistische Auswertung.</p>
<button type="button" onClick={() => decide('essential')}>
Nur notwendige
</button>
<button type="button" onClick={() => decide('custom')}>
Auswahl anpassen
</button>
<button type="button" onClick={() => decide('all')}>
Alle akzeptieren
</button>
</div>
)
}
Drei Punkte, die in der Pattern-Beschreibung stecken:
loadAnalytics() läuft erst nach Klick auf "Alle akzeptieren". Es
gibt keinen Code-Pfad, der Skripte vor der Einwilligung lädt.
Die drei Buttons sind in DOM-Reihenfolge identisch und tragen das
gleiche Styling. Wer "Alle akzeptieren" als <button> und
"Ablehnen" als <a>-Link rendert, hat schon verloren.
Der Banner ist mit role="dialog" und aria-labelledby annotiert,
damit Screenreader ihn als Modal erkennen.
Art. 7 Abs. 3 verlangt, dass der Widerruf so einfach ist wie das
Erteilen. Konkret heißt das: ein dauerhaft erreichbarer Link
("Cookie-Einstellungen") in der Fußzeile, der den Banner
wiederöffnet. Ohne diesen Link verstößt jedes Banner gegen die DSGVO,
egal wie sauber die Initial-Logik ist.
Server-seitige IP-Logs für Sicherheits- und Betriebszwecke sind
nach Erwägungsgrund 49 DSGVO ohne Einwilligung zulässig. Das gilt
nicht, wenn dieselben Logs an Analytics weitergeleitet werden — dann
ist es Tracking und unterliegt TTDSG §25.
In acht von zehn DSGVO-Audits scheitern Banner an Punkt 3: Tracking-
Skripte laden vor der Einwilligung. Häufigste Ursache: Google Tag
Manager wird unbedacht in <head> eingebunden, Tags feuern bei
page_view, der Banner ist Cosmetics. Lösung ist trivial — der GTM-
Loader prüft consentState === 'all'.
Der Seitenbefund-Kurzcheck prüft passiv, welche
Hosts vor der Einwilligung kontaktiert werden, und listet die
Erstaufträge auf.