Cookie Consent — Preferences
blockmarketingGranular consent dialog listing each cookie category with its purpose, retention, and a switch, with strictly-necessary shown as locked rather than hidden.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/cookie-consent-preferences.json$npx shadcn@latest add https://uipkge.dev/r/react/cookie-consent-preferences.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/cookie-consent-preferences.json$bunx shadcn@latest add https://uipkge.dev/r/react/cookie-consent-preferences.jsonNamed registry:
npx shadcn@latest add @uipkge-react/cookie-consent-preferencesInstalls to:components/blocks/Variants
Loading interactive previews…
Loading interactive previews…
Files installed (1)
components/blocks/CookieConsentPreferences.tsx3.4 kB
'use client' import { useState } from 'react' import { Lock } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Separator } from '@/components/ui/separator' import { Switch } from '@/components/ui/switch' const categories = [ { id: 'necessary', label: 'Strictly necessary', purpose: 'Session and sign-in state. Without these the app cannot keep you logged in.', retention: 'Session', locked: true, }, { id: 'analytics', label: 'Product analytics', purpose: 'Which pages are read and where people give up. Aggregated, never sold.', retention: '13 months', locked: false, }, { id: 'support', label: 'Support chat', purpose: 'Keeps a conversation open across page loads so you do not repeat yourself.', retention: '30 days', locked: false, }, ] export function CookieConsentPreferences() { const [open, setOpen] = useState(true) const [choices, setChoices] = useState<Record<string, boolean>>({ necessary: true, analytics: false, support: false, }) return ( <div data-slot="cookie-consent-preferences"> <Dialog open={open} onOpenChange={setOpen}> <DialogContent className="sm:max-w-lg"> <DialogHeader> <Badge variant="secondary" className="w-fit"> Privacy </Badge> <DialogTitle className="mt-3 text-xl">Choose what we store</DialogTitle> <DialogDescription> Each category says what it is for and how long it is kept. Nothing here is advertising. </DialogDescription> </DialogHeader> <Separator /> <ul className="space-y-4"> {categories.map((category) => ( <li key={category.id} className="flex items-start gap-4"> <div className="min-w-0 flex-1"> <div className="flex flex-wrap items-center gap-2"> <p className="text-sm font-medium">{category.label}</p> {category.locked && ( <Badge variant="outline" className="gap-1"> <Lock className="size-2.5" aria-hidden="true" /> Required </Badge> )} </div> <p className="text-muted-foreground mt-1 text-xs leading-relaxed">{category.purpose}</p> <p className="text-muted-foreground/80 mt-1 font-mono text-xs">Kept {category.retention}</p> </div> <Switch checked={choices[category.id]} onCheckedChange={(value: boolean) => setChoices((c) => ({ ...c, [category.id]: value }))} disabled={category.locked} aria-label={`Allow ${category.label}`} className="mt-1 shrink-0" /> </li> ))} </ul> <DialogFooter className="sm:justify-start"> <Button onClick={() => setOpen(false)}>Save choices</Button> <Button variant="outline" onClick={() => setOpen(false)}> Reject optional </Button> </DialogFooter> </DialogContent> </Dialog> </div> ) }
Raw manifest:https://uipkge.dev/r/react/cookie-consent-preferences.json