ExklusivBeta

Cookie Consent <mv-cookie-consent>

Nicht modales Cookie-Banner mit gleichwertigem Accept all und Reject all sowie einem Panel Choose, gespeichert mit einer Richtlinienversion in localStorage und einem First-Party-Cookie; anders als die meisten Banner sperrt es Skripte tatsächlich: <script type="text/plain" data-consent="analytics"> und data-src-Einbettungen laufen erst nach der Einwilligung, einmal, in Reihenfolge, mit erhaltenem CSP-Nonce.

KategorieOverlays
TypWeb Component (<mv-cookie-consent>)
StatusBeta
Keywordsexclusive, light, cookie, consent, gdpr, privacy, banner, gpc, script-gating

When to use

  • A site with EU or UK visitors must ask before loading analytics, ads or embeds, without a heavy third-party consent script
  • Tracking scripts should stay inert until the visitor opts in, and a footer Cookie settings link must reopen the choices
  • The privacy policy changed and every visitor has to be asked again

Avoid when

  • The message is a one-off notice or confirmation, not a consent choice → use Toast instead
  • Consent must be logged server-side for audits or shared across devices and domains; this stores it in the browser only
  • The site sets no optional cookies and loads no third-party trackers, so no banner is needed

Installation

node scripts/add.mjs cookie-consent --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["cookie-consent"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/observe.js, core/presence.js, components/cookie-consent/cookie-consent.js, components/cookie-consent/cookie-consent.css.

Verwendung

Schnellstart mit dem kleinsten funktionierenden Markup:

<mv-cookie-consent categories="analytics, marketing" version="1"></mv-cookie-consent>
<script type="text/plain" data-consent="analytics" src="/analytics.js"></script>

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<div id="mv-cc-demo" style="position:relative;width:100%;max-width:760px;min-height:600px;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-bg);overflow:hidden;display:flex;flex-direction:column">
  <header style="display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.75rem 1.1rem;border-bottom:1px solid var(--mv-border)">
    <strong style="display:flex;align-items:center;gap:.5rem;font-size:.95rem">
      <span style="width:1.4rem;height:1.4rem;border-radius:.4rem;background:linear-gradient(135deg,var(--mv-accent),var(--mv-accent-fg))"></span>
      Northwind Travel
    </strong>
    <nav style="display:flex;gap:1rem;font-size:.85rem;color:var(--mv-fg-muted)"><span>Trips</span><span>Journal</span><span>Help</span></nav>
  </header>

  <main style="flex:1;display:grid;gap:1rem;padding:1.4rem 1.1rem;align-content:start">
    <div>
      <p style="margin:0 0 .25rem;font-size:.75rem;color:var(--mv-fg-subtle)">Kyoto · 6 nights · from $1,240</p>
      <h3 style="margin:0;font-size:1.25rem">Autumn in the Higashiyama hills</h3>
      <p style="margin:.4rem 0 0;max-width:46ch;font-size:.9rem;color:var(--mv-fg-muted)">Temple gardens at first light, a tea lesson in Gion and a night train to Kanazawa. Departs October 18, 2026.</p>
    </div>
    <dl style="display:grid;grid-template-columns:auto 1fr;gap:.35rem 1rem;margin:0;justify-self:end;padding:.75rem 1rem;border:1px dashed var(--mv-border-strong);border-radius:var(--mv-radius-lg);font-size:.8rem">
      <dt style="color:var(--mv-fg-subtle)">Analytics script</dt><dd id="mv-cc-demo-analytics" style="margin:0;font-weight:500">Blocked</dd>
      <dt style="color:var(--mv-fg-subtle)">Marketing pixel</dt><dd id="mv-cc-demo-marketing" style="margin:0;font-weight:500">Blocked</dd>
      <dt style="color:var(--mv-fg-subtle)">Last choice</dt><dd id="mv-cc-demo-last" style="margin:0;color:var(--mv-fg-muted)">None yet</dd>
    </dl>
  </main>

  <footer style="display:flex;align-items:center;justify-content:flex-end;gap:1rem;padding:.6rem 1.1rem;border-top:1px solid var(--mv-border);font-size:.8rem;color:var(--mv-fg-muted)">
    <a href="#" data-consent-open style="color:inherit">Cookie settings</a>
    <button class="mv-button" data-variant="ghost" data-size="sm" id="mv-cc-demo-reset" type="button">Forget my choice</button>
  </footer>

  <mv-cookie-consent contained categories="analytics, marketing" version="2" storage-key="northwind-consent" policy="#privacy">
    <p data-category="analytics" data-label="Analytics">Anonymous visit counts, hosted in the EU. Helps us see which trips people read.</p>
  </mv-cookie-consent>
</div>

<!-- Inert until the visitor allows the category, then run once. -->
<script type="text/plain" data-consent="analytics">
  document.getElementById("mv-cc-demo-analytics").textContent = "Loaded";
</script>
<script type="text/plain" data-consent="marketing">
  document.getElementById("mv-cc-demo-marketing").textContent = "Loaded";
</script>

<script type="module">
  const root = document.getElementById("mv-cc-demo");
  const last = document.getElementById("mv-cc-demo-last");
  const fmt = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
  root.addEventListener("mv-consent", (e) => {
    const on = Object.entries(e.detail.categories).filter(([id, v]) => v && id !== "necessary").map(([id]) => id);
    last.textContent = `${e.detail.method} · ${on.length ? on.join(", ") : "necessary only"} · ${fmt.format(e.detail.date)}`;
  });
  document.getElementById("mv-cc-demo-reset").addEventListener("click", async () => {
    await customElements.whenDefined("mv-cookie-consent");
    customElements.get("mv-cookie-consent").reset();
    last.textContent = "None yet";
  });
</script>

API

Attributes

NameTypDefaultDescription
categoriesstring (comma or space list)analytics, marketingOptional categories the site uses. "necessary" is always on and always listed. Without the attribute, the ids of the category child elements, or analytics and marketing.
versionstring1Policy version stored with the choice. Changing it asks every visitor again (their previous switches are pre-filled). Adding a category also asks again.
storage-keystringmv-consentlocalStorage key and first-party cookie name. The cookie reads v=<version>&at=<ms>&analytics=1&marketing=0, so the server can read it too.
policyURLAdds a “Privacy policy” link under the message.
containedbooleanPositions the banner at the bottom-start of the nearest positioned ancestor instead of the viewport (previews, app shells).
data-consent (on any script, iframe, img…)category list<script type="text/plain" data-consent="analytics"> (inline or src) is re-created as a real script once every listed category is granted; data-type="module" sets the final type. An element with data-consent and data-src gets its src. Elements added later (SPA) are handled too.
data-consent-open (on any element)booleanClicking it reopens the banner with the choices panel, e.g. a footer “Cookie settings” link. Focus returns to it after saving.

Properties

NameTypDescription
stringsobjectEvery visible and announced text: heading, message, accept, reject, choose, save, policy, panel, alwaysOn, gpc, saved, and categories: { id: { label, description } }. Merged with the English defaults (DEFAULT_STRINGS export).
gpcboolean (read-only)True when the browser sends Global Privacy Control.

Methods

NameDescription
MvCookieConsent.get()Static. The current valid choice { version, date, categories: { necessary: true, analytics: false, … } }, or null when the visitor has not decided or must be asked again.
MvCookieConsent.has(category)Static. true when the category is granted ("necessary" is always true).
MvCookieConsent.open(opener?) / open(opener?)Shows the banner with the choices panel open and moves focus to the first switch.
MvCookieConsent.reset()Static. Forgets the stored choice and shows the banner again (scripts that already ran stay until the page reloads).
accept() / reject() / save()Same as the three buttons; save() stores the switches of the panel.

Events

NameDescription
mv-consentAfter each choice. detail = { version, date, categories, method: "accept" | "reject" | "custom", revoked: string[], gpc }. revoked lists categories that were granted before: scripts that already ran cannot be unloaded, so reload the page if you need them gone.

Content structure

NameDescription
(children)Your own message (paragraphs, links) replaces the default text.
[data-category="id"][data-label]A child element per category: data-label is the name, its content the description (overrides strings.categories).

CSS classes

NameDescription
mv-cookie-consent-card / -title / -message / -policy / -gpc / -panel / -list / -item / -row / -name / -desc / -always / -switch / -actions / -button / -chooseGenerated parts, all stylable.

CSS variables

NameDefaultDescription
--mv-cookie-consent-width25remBanner width (capped by the viewport).
--mv-cookie-consent-offsetvar(--mv-space-4)Distance from the bottom and start edges.
--mv-cookie-consent-button-bg6% fg over the cardBackground of Accept all, Reject all and Save choices. One variable for all three, so the two answers always weigh the same.
--mv-cookie-consent-button-fgvar(--mv-fg)Text color of those buttons.

Accessibility

A non-modal region labelled by its heading (h2): no backdrop, no focus trap, page stays usable, focus is never taken on load. Place the tag early in <body> so keyboard and screen reader users meet it first. Accept all and Reject all are identical buttons side by side, Reject never hidden behind Choose. Choose is a disclosure (aria-expanded, aria-controls); opening it moves focus to the first switch; Escape closes it and returns focus to Choose. When reopened from a data-consent-open link, Escape closes the banner and focus goes back to the link, also after saving. Category switches are native checkboxes with role=switch, labelled by the name and described by the description; necessary shows “Always on” as text. After a choice a polite status says “Your cookie choices were saved.” once. Optional switches start off (opt-in). Global Privacy Control: when navigator.globalPrivacyControl is true, Accept all leaves marketing (ids marketing, advertising, ads) off and a note says so; the visitor can still turn it on in Choose. Forced colors: system borders, native checkboxes, Highlight outlines. Reduced motion: no slide, instant fade. This component is a technical tool, not legal advice: the categories, texts and retention (about six months) must match your own policy.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden