# Consent

A cookie consent notice, as a card or a bar.

Source: [consent.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/consent.tsx)

```tsx
import { Consent } from "@/components/ui/consent";
import { toast } from "@/components/ui/toast";

export default function ConsentCard() {
  return (
    <Consent
      dismissOnConsent={false}
      onConsentChange={(decision) =>
        toast.add({ title: decision === "accepted" ? "Cookies accepted" : "Cookies declined" })
      }
      className="w-80 max-w-full"
    >
      We use cookies to understand how you use our service. <a href="#cookies">Learn more</a>
    </Consent>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/consent
```

## Usage

```tsx
import * as React from "react";

import { Consent } from "@/components/ui/consent";

export function CookieNotice() {
  const [open, setOpen] = React.useState(false);

  React.useEffect(() => {
    setOpen(localStorage.getItem("cookie-consent") === null);
  }, []);

  return (
    <Consent
      open={open}
      onOpenChange={setOpen}
      onConsentChange={(decision) => localStorage.setItem("cookie-consent", decision)}
      className="fixed inset-x-4 bottom-4 z-50 sm:left-auto sm:w-96"
    />
  );
}
```

Consent doesn't store the choice. Save it in `onConsentChange` and use `open` to keep the notice
hidden afterward. Read the saved choice in an effect, as above, so server-rendered pages don't
flash the notice.

Render it near the start of the page so it comes early in the tab order. It hides once a choice is
made, unless you set `dismissOnConsent={false}`.

## Examples

### Bar

Given more width, the notice lays out as a single row. Pass your own text as children, and change
the buttons with `acceptLabel` and `declineLabel`. If it isn't about cookies, give it a matching
`aria-label`.

```tsx
import { Consent } from "@/components/ui/consent";
import { toast } from "@/components/ui/toast";

export default function ConsentBar() {
  return (
    <Consent
      dismissOnConsent={false}
      aria-label="Analytics consent"
      acceptLabel="Allow analytics"
      declineLabel="No thanks"
      onConsentChange={(decision) =>
        toast.add({
          title: decision === "accepted" ? "Analytics allowed" : "Analytics turned off",
        })
      }
      className="w-full max-w-2xl"
    >
      We use privacy-friendly analytics to see which pages need work.{" "}
      <a href="#privacy">Read the privacy policy</a>
    </Consent>
  );
}
```

### Changing a choice

Control `open` to show the notice again, such as from a footer link.

```tsx
import * as React from "react";

import { Button } from "@/components/ui/button";
import { Consent, type ConsentDecision } from "@/components/ui/consent";

export default function ConsentControlled() {
  const [decision, setDecision] = React.useState<ConsentDecision | null>(null);

  return (
    <div className="flex w-80 max-w-full flex-col items-start gap-3">
      <Consent open={decision === null} onConsentChange={setDecision} className="w-full" />
      {decision !== null && (
        <>
          <p className="text-sm text-muted-foreground">
            Cookies {decision === "accepted" ? "accepted" : "declined"}.
          </p>
          <Button size="sm" onClick={() => setDecision(null)}>
            Change your choice
          </Button>
        </>
      )}
    </div>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `Consent` | `<div>` |

### Consent

| Prop                | Type                                    | Description                                                                    |
| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------ |
| `open?`             | `boolean`                               | Whether the notice is shown, when you control it.                              |
| `defaultOpen?`      | `boolean`                               | Whether the notice is shown at first, when it controls itself. Default: `true` |
| `onOpenChange?`     | `((open: boolean) => void)`             |                                                                                |
| `onConsentChange?`  | `((decision: ConsentDecision) => void)` | Called with the choice, for you to save.                                       |
| `dismissOnConsent?` | `boolean`                               | Hides the notice once a choice is made. Default: `true`                        |
| `acceptLabel?`      | `React.ReactNode`                       | Default: `"Accept"`                                                            |
| `declineLabel?`     | `React.ReactNode`                       | Default: `"Decline"`                                                           |
