I-010 Interaction medium

How to create a cookie consent banner Cookie コンセントバナーの作り方

GDPR-style cookie consent banner that slides in from the bottom on first visit with Accept/Decline actions. 初回訪問時にスライドインするGDPRスタイルのCookieコンセントバナー。Accept/Declineの操作で非表示に。

ライブデモ Live Demo

Cookie 設定 Cookie Preferences

使用するCookieの種類を選択できます。 Choose which cookies you allow.

必須 Necessary Required
分析 Analytics
マーケティング Marketing
機能性 Functional
当サイトではCookieを使用しています。「設定」から種類を選択できます。 We use cookies. Click "Settings" to customize.

概要・用途・特徴Overview, Usage & Features

何ができるかWhat it does

GDPR-style cookie consent banner that slides in from the bottom on first visit with Accept/Decline actions.

初回訪問時にスライドインするGDPRスタイルのCookieコンセントバナー。Accept/Declineの操作で非表示に。

どこで使うかWhere to use

user engagement, data entry, content management, settings panel

GDPRが適用されるWebサイト、eコマースサイト、アナリティクス計測サイト、SaaSアプリ

特徴Key features

GDPR/CCPA-compliant cookie consent banner with Accept and Decline actions. Persists user choice in localStorage to prevent repeat displays. Smooth slide-in animation from bottom. Fully keyboard navigable. Customizable button labels and link to privacy policy.

GDPR/CCPA準拠のクッキー同意バナー。AcceptとDeclineアクション付き。localStorage にユーザー選択を保存して再表示を防止。ボトムからのスムーズなスライドインアニメーション。フルキーボードナビゲーション対応。ボタンラベルとプライバシーポリシーリンクをカスタマイズ可能。

調整可能パラメータ Adjustable Parameters

Parameter Default Description

実装コード Implementation Code

// react/I-010.jsx
import { useState, useEffect } from "react";
import "./I-010.css";

const CATEGORIES = [
  { key: "necessary",  label: "Required Cookies",   required: true  },
  { key: "analytics",  label: "Analytics Cookies",  required: false },
  { key: "marketing",  label: "Marketing Cookies",  required: false },
  { key: "functional", label: "Functional Cookies", required: false },
];

// Change the message prop to update the banner text
export default function CookieConsentBanner({
  message = "This site uses cookies for usability, analytics, and advertising. Click \"Accept All\" to consent to all cookies.",
  storageKey = "cookie-consent",
  onSave,
}) {
  const [visible, setVisible] = useState(false);
  const [panelOpen, setPanelOpen] = useState(false);
  const [prefs, setPrefs] = useState(() => {
    const init = {};
    CATEGORIES.forEach(c => { init[c.key] = c.required; });
    return init;
  });

  useEffect(() => {
    if (!localStorage.getItem(storageKey)) {
      const t = setTimeout(() => setVisible(true), 800);
      return () => clearTimeout(t);
    }
  }, [storageKey]);

  const dismiss = (values) => {
    localStorage.setItem(storageKey, JSON.stringify(values));
    setVisible(false);
    setPanelOpen(false);
    if (onSave) onSave(values);
  };

  if (!visible) return null;

  return (
    <>
      <div className="cookie-banner visible">
        <p className="cookie-banner-text">{message}</p>
        <div className="cookie-actions">
          <button className="cookie-btn decline"
            onClick={() => dismiss({ necessary:true, analytics:false,
                                      marketing:false, functional:false })}>
            Decline
          </button>
          <button className="cookie-btn settings"
            onClick={() => setPanelOpen(true)}>
            Settings
          </button>
          <button className="cookie-btn accept"
            onClick={() => dismiss({ necessary:true, analytics:true,
                                      marketing:true, functional:true })}>
            Accept All
          </button>
        </div>
      </div>

      {panelOpen && (
        <div className="cookie-overlay open"
          onClick={e => e.target === e.currentTarget && setPanelOpen(false)}>
          <div className="cookie-panel">
            <h3>Cookie Settings</h3>
            {CATEGORIES.map(cat => (
              <div className="cookie-cat" key={cat.key}>
                <span>{cat.label}</span>
                <input type="checkbox"
                  checked={prefs[cat.key]}
                  disabled={cat.required}
                  onChange={e =>
                    setPrefs(p => ({ ...p, [cat.key]: e.target.checked }))
                  } />
              </div>
            ))}
            <button onClick={() => dismiss(prefs)}>Save Preferences</button>
          </div>
        </div>
      )}
    </>
  );
}
/* (3) Banner */
.cookie-banner {
  position: fixed; bottom: 0; left: 0; right: 0;
  background: #0f1126; color: #f6f6fe;
  padding: 16px 24px; display: flex;
  align-items: center; gap: 16px; z-index: 9999;
  transform: translateY(100%);
  transition: transform .5s cubic-bezier(.16,1,.3,1);
}
.cookie-banner.visible { transform: translateY(0); }
.cookie-banner-text { flex: 1; font-size: 14px; opacity: .9; }
.cookie-actions { display: flex; gap: 8px; }
.cookie-btn {
  padding: 8px 18px; border-radius: 6px;
  font-weight: 600; cursor: pointer; border: none;
}
.cookie-btn.accept { background: #5c6ac4; color: #fff; }
.cookie-btn.settings {
  background: transparent; color: #f6f6fe;
  border: 1px solid rgba(255,255,255,.25);
}
.cookie-btn.decline {
  background: transparent; color: rgba(255,255,255,.6);
  border: 1px solid rgba(255,255,255,.15);
}

/* ④ 設定パネル */
.cookie-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.5); z-index: 10000;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none; transition: opacity .3s;
}
.cookie-overlay.open { opacity: 1; pointer-events: auto; }
.cookie-panel {
  background: #fff; color: #1a1a2e;
  border-radius: 16px; padding: 28px 32px;
  width: 90%; max-width: 520px;
}
.cookie-cat {
  display: flex; justify-content: space-between;
  padding: 14px 0; border-top: 1px solid #e5e7eb;
}
import { useState, useEffect } from 'react';
import './I-010.css';

const DEFAULT_CATEGORIES = [
  { key: 'necessary',  labelJa: '必須Cookie',         labelEn: 'Necessary',  descJa: 'サイトの基本機能に必要', descEn: 'Essential for basic functionality', required: true },
  { key: 'analytics',  labelJa: '分析Cookie',         labelEn: 'Analytics',  descJa: '利用状況の分析',         descEn: 'Understand how visitors use the site', required: false },
  { key: 'marketing',  labelJa: 'マーケティングCookie', labelEn: 'Marketing',  descJa: 'パーソナライズ広告',     descEn: 'Personalized advertisements',         required: false },
  { key: 'functional', labelJa: '機能性Cookie',        labelEn: 'Functional', descJa: '言語・テーマの記憶',     descEn: 'Remember your preferences',           required: false },
];

export default function CookieConsentBanner({
  message = 'We use cookies to enhance your browsing experience.',
  acceptLabel = 'Accept All',
  declineLabel = 'Decline',
  settingsLabel = 'Settings',
  storageKey = 'cookie-consent',
  categories = DEFAULT_CATEGORIES,
  onSave,
}) {
  const [visible, setVisible] = useState(false);
  const [hiding, setHiding] = useState(false);
  const [panelOpen, setPanelOpen] = useState(false);
  const [prefs, setPrefs] = useState(() => {
    const init = {};
    categories.forEach(c => { init[c.key] = c.required || false; });
    return init;
  });

  useEffect(() => {
    const stored = localStorage.getItem(storageKey);
    if (!stored) {
      const timer = setTimeout(() => setVisible(true), 800);
      return () => clearTimeout(timer);
    }
  }, [storageKey]);

  const dismiss = (values) => {
    localStorage.setItem(storageKey, JSON.stringify(values));
    setPanelOpen(false);
    setHiding(true);
    setTimeout(() => { setVisible(false); setHiding(false); }, 500);
    if (onSave) onSave(values);
  };

  const acceptAll = () => {
    const all = {};
    categories.forEach(c => { all[c.key] = true; });
    dismiss(all);
  };

  const declineAll = () => {
    const minimal = {};
    categories.forEach(c => { minimal[c.key] = c.required; });
    dismiss(minimal);
  };

  if (!visible) return null;

  return (
    <>
      <div className={`cookie-banner ${hiding ? 'hiding' : 'visible'}`}>
        <div className="cookie-banner-content">
          <p>{message}</p>
        </div>
        <div className="cookie-banner-actions">
          <button className="cookie-btn cookie-btn-decline" onClick={declineAll}>
            {declineLabel}
          </button>
          <button className="cookie-btn cookie-btn-settings" onClick={() => setPanelOpen(true)}>
            {settingsLabel}
          </button>
          <button className="cookie-btn cookie-btn-accept" onClick={acceptAll}>
            {acceptLabel}
          </button>
        </div>
      </div>

      {panelOpen && (
        <div className="cookie-overlay open" onClick={(e) => e.target === e.currentTarget && setPanelOpen(false)}>
          <div className="cookie-panel">
            <h3>Cookie Preferences</h3>
            <p>Choose which cookies you allow. Necessary cookies are always enabled.</p>
            {categories.map(cat => (
              <div className="cookie-cat" key={cat.key}>
                <div className="cookie-cat-info">
                  <strong>
                    {cat.labelEn}
                    {cat.required && <span className="cookie-required">Required</span>}
                  </strong>
                  <span>{cat.descEn}</span>
                </div>
                <label className="cookie-toggle">
                  <input
                    type="checkbox"
                    checked={prefs[cat.key]}
                    disabled={cat.required}
                    onChange={(e) => setPrefs(p => ({ ...p, [cat.key]: e.target.checked }))}
                  />
                  <span className="slider" />
                </label>
              </div>
            ))}
            <div className="cookie-panel-actions">
              <button className="cookie-panel-btn cookie-panel-btn-cancel" onClick={() => setPanelOpen(false)}>
                Cancel
              </button>
              <button className="cookie-panel-btn cookie-panel-btn-save" onClick={() => dismiss(prefs)}>
                Save Preferences
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}
/* --- Banner --- */
.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #0f1126;
  color: #f6f6fe;
  padding: 16px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  z-index: 10000;
  transform: translateY(100%);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2);
  font-family: "Inter", "Noto Sans JP", system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.5;
}

.cookie-banner.visible {
  transform: translateY(0);
}

.cookie-banner.hiding {
  transform: translateY(100%);
}

.cookie-banner-content {
  flex: 1;
}

.cookie-banner-content p {
  margin: 0;
  opacity: 0.9;
}

.cookie-banner-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.cookie-btn {
  padding: 8px 18px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
  border: none;
  font-family: inherit;
}

.cookie-btn-accept {
  background: #5c6ac4;
  color: #fff;
}

.cookie-btn-accept:hover {
  background: #4a5ab8;
  transform: translateY(-1px);
}

.cookie-btn-settings {
  background: transparent;
  color: #f6f6fe;
  border: 1px solid rgba(255, 255, 255, 0.25);
}

.cookie-btn-settings:hover {
  background: rgba(255, 255, 255, 0.08);
}

.cookie-btn-decline {
  background: transparent;
  color: rgba(246, 246, 254, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.15);
}

.cookie-btn-decline:hover {
  background: rgba(255, 255, 255, 0.05);
  color: #f6f6fe;
}

/* --- Overlay --- */
.cookie-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 10001;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
}

.cookie-overlay.open {
  opacity: 1;
  pointer-events: auto;
}

/* --- Panel --- */
.cookie-panel {
  background: #fff;
  color: #1a1a2e;
  border-radius: 16px;
  width: 90%;
  max-width: 520px;
  max-height: 85vh;
  overflow-y: auto;
  padding: 28px 32px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  font-family: "Inter", "Noto Sans JP", system-ui, sans-serif;
  transform: translateY(20px);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

.cookie-overlay.open .cookie-panel {
  transform: translateY(0);
}

.cookie-panel h3 {
  margin: 0 0 4px;
  font-size: 18px;
  font-weight: 700;
}

.cookie-panel > p {
  margin: 0 0 20px;
  font-size: 13px;
  color: #6b7280;
  line-height: 1.5;
}

/* --- Category rows --- */
.cookie-cat {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 0;
  border-top: 1px solid #e5e7eb;
}

.cookie-cat-info {
  flex: 1;
}

.cookie-cat-info strong {
  font-size: 14px;
  display: block;
  margin-bottom: 2px;
}

.cookie-cat-info span {
  font-size: 12px;
  color: #6b7280;
  line-height: 1.4;
}

.cookie-required {
  font-size: 10px;
  color: #5c6ac4;
  background: #eef0fb;
  border-radius: 4px;
  padding: 2px 6px;
  margin-left: 6px;
  font-weight: 600;
  vertical-align: middle;
}

/* --- Toggle --- */
.cookie-toggle {
  position: relative;
  width: 44px;
  height: 24px;
  flex-shrink: 0;
  margin-top: 2px;
}

.cookie-toggle input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}

.cookie-toggle .slider {
  position: absolute;
  inset: 0;
  background: #d1d5db;
  border-radius: 24px;
  cursor: pointer;
  transition: background 0.2s;
}

.cookie-toggle .slider::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.cookie-toggle input:checked + .slider {
  background: #5c6ac4;
}

.cookie-toggle input:checked + .slider::after {
  transform: translateX(20px);
}

.cookie-toggle input:disabled + .slider {
  background: #5c6ac4;
  opacity: 0.6;
  cursor: default;
}

.cookie-toggle input:disabled + .slider::after {
  transform: translateX(20px);
}

/* --- Panel actions --- */
.cookie-panel-actions {
  display: flex;
  gap: 10px;
  margin-top: 20px;
  justify-content: flex-end;
}

.cookie-panel-btn {
  padding: 10px 22px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
  border: none;
  font-family: inherit;
}

.cookie-panel-btn-save {
  background: #5c6ac4;
  color: #fff;
}

.cookie-panel-btn-save:hover {
  background: #4a5ab8;
}

.cookie-panel-btn-cancel {
  background: #f3f4f6;
  color: #374151;
}

.cookie-panel-btn-cancel:hover {
  background: #e5e7eb;
}

/* --- Responsive --- */
@media (max-width: 600px) {
  .cookie-banner {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 16px 20px;
  }

  .cookie-banner-actions {
    justify-content: flex-end;
  }
}

仕組みとカスタマイズHow It Works & Customization

仕組みHow it works

On page load, JavaScript checks localStorage for a stored consent decision. If none exists, the banner slides in after a short delay. Clicking Accept or Decline writes the choice to localStorage and slides the banner out. Acceptance can trigger analytics initialization (e.g., gtag()) while Decline leaves trackers off.

ページ読み込み時にJavaScriptがlocalStorageに保存された同意決定を確認。存在しない場合、短い遅延後にバナーがスライドイン。AcceptまたはDeclineのクリックで選択をlocalStorageに書き込みバナーをスライドアウト。同意を受け入れるとアナリティクス初期化(例:gtag())をトリガーし、拒否するとトラッカーをオフのままにします。

カスタマイズ方法Customization

Add category toggles (Analytics, Marketing, Preferences) for granular consent management. Persist choice server-side via a cookie or API call for cross-device consistency. Localize the text by detecting navigator.language.

細粒度の同意管理のためにカテゴリトグル(アナリティクス・マーケティング・設定)を追加。クロスデバイスの一貫性のためにCookieまたはAPIコールでサーバーサイドに選択を保存。navigator.languageを検出してテキストをローカライズ。

注意点Caveats

Pre-ticked consent boxes or "Accept" as the only prominent button violate GDPR requirements. Ensure Decline is equally prominent. Do not load any tracking scripts before consent is obtained. Re-ask for consent if the privacy policy changes significantly.

事前チェック済みの同意ボックスや「承認」のみを目立つボタンにすることはGDPR要件違反です。「拒否」も同等に目立つようにしてください。同意を得る前にトラッキングスクリプトを読み込まないでください。プライバシーポリシーが大幅に変更された場合は再度同意を求めてください。

よくある質問 Frequently Asked Questions

How to customize the cookie consent banner? Cookie Consent Bannerをカスタマイズするには?

Modify the CSS custom properties and class styles defined in the code section. Key adjustable values include colors, sizes, durations, and spacing. See the Adjustable Parameters section for specific variables.

コードセクションで定義されているCSSカスタムプロパティとクラススタイルを変更してください。色、サイズ、時間、間隔が主な調整可能値です。具体的な変数は調整可能パラメータセクションを参照してください。

How to use the cookie consent banner in React? ReactでCookie Consent Bannerを使うには?

Import the provided React component and its CSS file. The component accepts props for customization. Check the React code section for the full implementation and available props.

提供されているReactコンポーネントとCSSファイルをインポートしてください。コンポーネントのpropsでカスタマイズできます。完全な実装と利用可能なpropsはReactコードセクションを参照してください。

What are the performance implications of cookie consent banner? Cookie Consent Bannerのパフォーマンスへの影響は?

This implementation uses CSS transforms and opacity for animations, which are GPU-accelerated. It's lightweight and doesn't cause layout thrashing. Consider using prefers-reduced-motion for accessibility.

この実装はCSSのtransformとopacityを使用しており、GPUアクセラレーションされます。軽量でレイアウトスラッシングを引き起こしません。アクセシビリティのためにprefers-reduced-motionの使用を検討してください。

AIへの指示テンプレート AI Prompt Template

以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.