Menu

Change language

GitHub repo

Cookie Consent Banner

blocklegal

GDPR-style cookie consent banner with two variants: a full-width bottom bar and a compact card. Customize expands inline preferences with Necessary (always on), Analytics, and Marketing switches. Accept, Reject, and Save dismiss with a 150ms fade.

Installation

$npx uipkge-ng@latest add cookie-consent-banner
Manifest:https://uipkge.dev/r/angular/cookie-consent-banner.jsonInstalls to:src/app/components/blocks/cookie-consent-banner/

Variants

Loading interactive previews…

npm dependencies

Files installed (2)

  • src/app/components/blocks/cookie-consent-banner/cookie-consent-banner.component.ts8.3 kB
    // GDPR-style cookie consent banner with two variants: a full-width bottom bar and a compact
    // card. Customize expands inline preferences with Necessary (always on), Analytics, and
    // Marketing switches. Accept, Reject, and Save dismiss with a 150ms fade.
    // Port of the Vue/React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'
    import { Cookie, LucideAngularModule } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import { UiSwitchComponent } from '@/ui/switch/switch.component'
    
    export type CookieConsentBannerVariant = 'bottom-bar' | 'card'
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-cookie-consent-banner, [ui-cookie-consent-banner]',
      standalone: true,
      // The block renders its own root <div>: keep this wrapper out of the layout.
      host: { '[attr.class]': '"contents"' },
      imports: [LucideAngularModule, UiButtonComponent, UiSeparatorComponent, UiSwitchComponent],
      template: `
        @if (visible()) {
          <div data-slot="cookie-consent-banner" role="dialog" aria-label="Cookie consent" [class]="rootClass">
            @if (variant === 'bottom-bar') {
              <div class="bg-card border-border w-full border-t px-4 py-4 sm:px-6">
                <div class="mx-auto flex max-w-5xl flex-col gap-4">
                  <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
                    <div class="flex items-start gap-3">
                      <span
                        class="bg-primary/10 text-primary mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full"
                      >
                        <lucide-icon [img]="Cookie" class="size-4" aria-hidden="true" />
                      </span>
                      <p class="text-muted-foreground max-w-xl text-sm leading-relaxed">
                        We use cookies to improve your experience and analyze traffic. Read our
                        <a
                          href="#"
                          class="text-foreground focus-visible:ring-ring rounded-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-2"
                          >Cookie Policy</a
                        >.
                      </p>
                    </div>
                    <div class="flex shrink-0 flex-wrap items-center gap-2">
                      <button ui-button variant="ghost" size="sm" (click)="showPreferences.set(!showPreferences())">
                        {{ showPreferences() ? 'Hide preferences' : 'Customize' }}
                      </button>
                      <button ui-button variant="outline" size="sm" (click)="dismiss()">Reject</button>
                      <button ui-button size="sm" (click)="dismiss()">Accept all</button>
                    </div>
                  </div>
    
                  @if (showPreferences()) {
                    <div class="flex flex-col gap-3">
                      <ui-separator />
                      <div class="flex items-center justify-between gap-4 py-1">
                        <div>
                          <p class="text-sm font-medium">Necessary</p>
                          <p class="text-muted-foreground text-xs">Required for the site to function.</p>
                        </div>
                        <ui-switch [checked]="true" disabled aria-label="Necessary cookies" />
                      </div>
                      <div class="flex items-center justify-between gap-4 py-1">
                        <div>
                          <p class="text-sm font-medium">Analytics</p>
                          <p class="text-muted-foreground text-xs">Helps us understand usage patterns.</p>
                        </div>
                        <ui-switch
                          [checked]="analytics()"
                          (checkedChange)="analytics.set($event)"
                          aria-label="Analytics cookies"
                        />
                      </div>
                      <div class="flex items-center justify-between gap-4 py-1">
                        <div>
                          <p class="text-sm font-medium">Marketing</p>
                          <p class="text-muted-foreground text-xs">Used to personalize campaigns.</p>
                        </div>
                        <ui-switch
                          [checked]="marketing()"
                          (checkedChange)="marketing.set($event)"
                          aria-label="Marketing cookies"
                        />
                      </div>
                      <div class="mt-1 flex justify-end">
                        <button ui-button size="sm" (click)="dismiss()">Save preferences</button>
                      </div>
                    </div>
                  }
                </div>
              </div>
            } @else {
              <div class="bg-card border-border mx-auto max-w-md rounded-xl border p-6 shadow-xs">
                <div class="flex flex-col gap-4">
                  <div class="flex items-start gap-3">
                    <span class="bg-primary/10 text-primary flex size-9 shrink-0 items-center justify-center rounded-full">
                      <lucide-icon [img]="Cookie" class="size-4" aria-hidden="true" />
                    </span>
                    <div>
                      <h3 class="text-sm font-semibold">We value your privacy</h3>
                      <p class="text-muted-foreground mt-1 text-sm leading-relaxed">
                        We use cookies to improve your experience and analyze traffic. Read our
                        <a
                          href="#"
                          class="text-foreground focus-visible:ring-ring rounded-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-2"
                          >Cookie Policy</a
                        >.
                      </p>
                    </div>
                  </div>
    
                  @if (showPreferences()) {
                    <ui-separator />
                    <div class="flex items-center justify-between gap-4 py-1">
                      <div>
                        <p class="text-sm font-medium">Necessary</p>
                        <p class="text-muted-foreground text-xs">Required for the site to function.</p>
                      </div>
                      <ui-switch [checked]="true" disabled aria-label="Necessary cookies" />
                    </div>
                    <div class="flex items-center justify-between gap-4 py-1">
                      <div>
                        <p class="text-sm font-medium">Analytics</p>
                        <p class="text-muted-foreground text-xs">Helps us understand usage patterns.</p>
                      </div>
                      <ui-switch
                        [checked]="analytics()"
                        (checkedChange)="analytics.set($event)"
                        aria-label="Analytics cookies"
                      />
                    </div>
                    <div class="flex items-center justify-between gap-4 py-1">
                      <div>
                        <p class="text-sm font-medium">Marketing</p>
                        <p class="text-muted-foreground text-xs">Used to personalize campaigns.</p>
                      </div>
                      <ui-switch
                        [checked]="marketing()"
                        (checkedChange)="marketing.set($event)"
                        aria-label="Marketing cookies"
                      />
                    </div>
                  }
    
                  <div class="flex flex-wrap items-center gap-2">
                    <button ui-button variant="ghost" size="sm" (click)="showPreferences.set(!showPreferences())">
                      {{ showPreferences() ? 'Hide preferences' : 'Customize' }}
                    </button>
                    <div class="ml-auto flex items-center gap-2">
                      <button ui-button variant="outline" size="sm" (click)="dismiss()">Reject</button>
                      <button ui-button size="sm" (click)="dismiss()">
                        {{ showPreferences() ? 'Save preferences' : 'Accept all' }}
                      </button>
                    </div>
                  </div>
                </div>
              </div>
            }
          </div>
        }
      `,
    })
    export class UiCookieConsentBannerComponent {
      protected readonly Cookie = Cookie
    
      @Input() variant: CookieConsentBannerVariant = 'bottom-bar'
      @Input('class') className?: string
    
      readonly visible = signal(true)
      readonly showPreferences = signal(false)
      readonly analytics = signal(true)
      readonly marketing = signal(false)
    
      get rootClass(): string {
        return cn('animate-in fade-in duration-150', this.className)
      }
    
      dismiss(): void {
        this.visible.set(false)
      }
    }
    
  • src/app/components/blocks/cookie-consent-banner/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/cookie-consent-banner.json