# Cookie Consent

A compact cookie consent card with pluggable accept and decline handlers.

## Installation

```bash
npx shadcn@latest add https://ui.jarv.is/r/cookie-consent.json
```

[Registry JSON](https://ui.jarv.is/r/cookie-consent.json)

## Preview

```tsx
import { CookieConsent } from "@/components/ui/cookie-consent";

export function Preview() {
  return <CookieConsent />;
}
```


## Source

### ui/cookie-consent.tsx

```tsx
"use client";

import * as React from "react";

import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

type CookieConsentDecision = "accepted" | "declined";

type CookieConsentProps = React.ComponentProps<"div"> & {
  acceptLabel?: React.ReactNode;
  defaultOpen?: boolean;
  declineLabel?: React.ReactNode;
  dismissOnConsent?: boolean;
  learnMoreHref?: string;
  learnMoreLabel?: React.ReactNode;
  onAccept?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  onConsentChange?: (
    decision: CookieConsentDecision,
    event: React.MouseEvent<HTMLButtonElement>,
  ) => void;
  onDecline?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  onOpenChange?: (open: boolean) => void;
  open?: boolean;
};

function CookieConsent({
  acceptLabel = "Accept",
  defaultOpen = true,
  declineLabel = "Decline",
  dismissOnConsent = true,
  learnMoreHref = "#",
  learnMoreLabel = "Learn more.",
  onAccept,
  onConsentChange,
  onDecline,
  onOpenChange,
  open,
  className,
  children,
  ...props
}: CookieConsentProps) {
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
  const isControlled = open !== undefined;
  const isOpen = isControlled ? open : uncontrolledOpen;

  const setOpen = React.useCallback(
    (nextOpen: boolean) => {
      if (!isControlled) {
        setUncontrolledOpen(nextOpen);
      }

      onOpenChange?.(nextOpen);
    },
    [isControlled, onOpenChange],
  );

  const handleConsent = React.useCallback(
    (decision: CookieConsentDecision, event: React.MouseEvent<HTMLButtonElement>) => {
      if (decision === "accepted") {
        onAccept?.(event);
      } else {
        onDecline?.(event);
      }

      onConsentChange?.(decision, event);

      if (dismissOnConsent && !event.defaultPrevented) {
        setOpen(false);
      }
    },
    [dismissOnConsent, onAccept, onConsentChange, onDecline, setOpen],
  );

  if (!isOpen) {
    return null;
  }

  return (
    <div
      role="dialog"
      aria-label="Cookie consent"
      data-slot="cookie-consent"
      className={cn("max-w-[260px] rounded-lg border bg-card p-3 shadow-md", className)}
      {...props}
    >
      <p className="text-xs leading-relaxed text-pretty text-muted-foreground">
        {children ?? (
          <>
            We use cookies to understand how you use our service.{" "}
            <a
              href={learnMoreHref}
              className="text-foreground/80 underline underline-offset-2 hover:text-foreground"
            >
              {learnMoreLabel}
            </a>
          </>
        )}
      </p>
      <div className="mt-2.5 flex items-center gap-1.5">
        <Button
          type="button"
          variant="ghost"
          size="xs"
          onClick={(event) => handleConsent("declined", event)}
        >
          {declineLabel}
        </Button>
        <Button type="button" size="xs" onClick={(event) => handleConsent("accepted", event)}>
          {acceptLabel}
        </Button>
      </div>
    </div>
  );
}

export { CookieConsent, type CookieConsentDecision, type CookieConsentProps };
```



## Usage

### Basic consent

Use controlled or uncontrolled state to connect the consent card to your preference storage.

```tsx
import { CookieConsent } from "@/components/ui/cookie-consent";

export function Example() {
  return <CookieConsent onConsentChange={(decision) => console.log(decision)} />;
}
```

### Store the decision

Use `onConsentChange` to persist the user decision in your own storage layer.

```tsx
import { CookieConsent } from "@/components/ui/cookie-consent";

export function ConsentBanner() {
  return (
    <CookieConsent
      learnMoreHref="/privacy"
      onConsentChange={(decision) => {
        localStorage.setItem("cookie-consent", decision);
      }}
    />
  );
}
```

### Controlled display

Control `open` when the consent state comes from a loader, server response, or settings page.

```tsx
"use client";

import { useState } from "react";

import {
  CookieConsent,
  type CookieConsentDecision,
} from "@/components/ui/cookie-consent";

function saveCookieDecision(decision: CookieConsentDecision) {
  localStorage.setItem("cookie-consent", decision);
}

export function ControlledConsent() {
  const [open, setOpen] = useState(true);

  return (
    <CookieConsent
      open={open}
      onOpenChange={setOpen}
      onConsentChange={(decision) => {
        saveCookieDecision(decision);
      }}
    />
  );
}
```

### Custom copy

Customize labels and body copy when the consent message needs to match your product policy.

```tsx
import { CookieConsent } from "@/components/ui/cookie-consent";

export function AnalyticsConsent() {
  return (
    <CookieConsent acceptLabel="Allow analytics" declineLabel="No thanks">
      We use privacy-friendly analytics to understand which product areas need work.{" "}
      <a href="/privacy" className="underline underline-offset-2">
        Read the privacy policy.
      </a>
    </CookieConsent>
  );
}
```

