Installation
npx shadcn@latest add @pixeldosa/ai-disclosureRequires `cn` at `@/lib/utils`. Write the `facts` as specifics about this feature, not policy boilerplate.
Usage
"use client";
import { AIDisclosure } from "@/components/ui/ai-disclosure";
<AIDisclosure
title="Written by AI, from your project"
summary="A draft reply, prepared from this thread and your saved tone."
facts={[
{ id: "used", label: "What it used", value: "The last 20 messages in this thread." },
{ id: "training", label: "Training", value: "Your content isn't used to train the model." },
]}
learnMoreHref="/privacy"
/>
Why it exists
People are entitled to know when AI wrote something, what it read to do that, and what happens to their data — and to be asked before a feature records, remembers or shares. Most products bury this in a policy page or a tooltip.
Features
- Two shapes, one voice.
noticestates the fact;consentasks first. - Specifics, not boilerplate.
factsare label-and-value pairs about this feature: what it used, how long it's kept, who can see it. - A notice stays one line with the details one click away.
- Consent shows the details up front. A decision taken without them isn't consent.
- Both answers look the same. Same size, same prominence, nothing preselected, and no dismiss — a question you can close is a question that gets closed.
Variants
A notice on AI-written content
This reply was drafted by AI
Prepared from this thread and your saved tone. Check it before sending.
- What it used
- The last 20 messages in this thread and your tone settings.
- How long it's kept
- The draft stays in this thread until you delete it.
Asking first
Record and summarise this meeting?
Everyone in the meeting is told. You can stop it at any point.
- Who can see it
- You and the three people on this call.
- How long it's kept
- 30 days, then deleted.
A short notice, no details
This summary was written by AI
Check anything you plan to act on.
Props
| Prop | Type | Notes |
|---|---|---|
title | string | Required. The fact, or the question. |
variant | "notice" | "consent" | Defaults to "notice". |
summary | string | One sentence in the user's words. |
facts | { id, label, value }[] | The specifics. Shown up front for consent. |
learnMoreHref / learnMoreLabel | string | Your policy page. |
onAllow / onDecline | () => void | Consent only. |
allowLabel / declineLabel | string | Say what happens: "Record and summarise". |
onDismiss | () => void | Notice only. |
headingLevel | 2 | 3 | 4 | 5 | 6 | Defaults to 3. |
Accessibility
- A labelled
<section>with a real heading. - The details toggle is a proper expander (
aria-expanded,aria-controls). - Consent buttons are ordinary buttons of equal weight, in reading order, with labels that say what will happen rather than "OK" and "Cancel".
When not to use
- For one consequential action awaiting a decision, use
AI Approval Gate. - As a cookie banner. This is about what a feature does with a person's content.
- To explain one output's sources, use
AI Context SurfaceorInline Citations.
why it's built this way
Engineering Notes
Notice and consent are one component because they share a sentence, a set of facts and a visual home; splitting them produced two surfaces that drifted apart in wording, which is how products end up disclosing one thing and asking about another. What differs is only what the variant does with the facts: a notice hides them behind a disclosure button so the common case stays a single line, while consent renders them open, because a decision taken without the specifics is not consent and an expander is an easy thing to skip. Both consent buttons carry identical styling by construction rather than by convention — there is no primary variant available to the caller — so a product cannot make "Allow" the obvious answer. The consent variant intentionally offers no dismiss: a question you can close is a question that gets closed.
how it moves, and when it doesn't
Motion Notes
Nothing animates in either variant. Consent is the user's turn at its most consequential, and a notice is a statement of fact, so neither has anything to express through motion; the only transitions are the standard instant colour changes on buttons, disabled under `prefers-reduced-motion`.