Snippets / UI Components /

Cookie/consent banner

GDPR-compliant consent banner with semantic HTML, focus management, and no-JS fallback.

Widely supported
uino-js

Quick implementation

/* HTML:
<div class="cookie-banner" role="alert" aria-live="polite">
  <h3>We value your privacy</h3>
  <p>We use cookies to enhance your browsing experience.</p>
  <div class="cookie-banner-actions">
    <button class="btn btn--accept">Accept all</button>
    <button class="btn btn--decline">Decline</button>
    <button class="btn btn--settings">Settings</button>
  </div>
</div>
*/

.cookie-banner {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 3rem);
  max-width: 40rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  padding: 1.25rem 1.5rem;
  box-shadow: 0 4px 24px oklch(0 0 0 / 0.12);
  display: flex;
  flex-direction: column;
  gap: 1rem;
  z-index: 1000;
}

.cookie-banner h3 {
  font-family: var(--font-display);
  font-size: 1rem;
  margin: 0;
  color: var(--text);
  letter-spacing: -0.01em;
}

.cookie-banner p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.cookie-banner-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.cookie-banner .btn {
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}

.cookie-banner .btn--accept {
  background: var(--accent);
  color: oklch(0.98 0 0);
  border: none;
}

.cookie-banner .btn--accept:hover {
  background: var(--accent-hover);
}

.cookie-banner .btn--decline {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--card-border);
}

.cookie-banner .btn--decline:hover {
  background: var(--subtle);
}

.cookie-banner .btn--settings {
  background: transparent;
  color: var(--muted);
  border: none;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.cookie-banner .btn--settings:hover {
  color: var(--text);
}

/* Hide banner when dismissed (add .hidden class via JS or :has) */
.cookie-banner.hidden {
  display: none;
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .cookie-banner .btn {
    transition: none;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer specializing in accessible UI components.

Goal: Build a GDPR-compliant cookie consent banner with semantic HTML and CSS-only styling.

Technical constraints:
- Use semantic HTML: <h3> for the title, <p> for description, <button> for actions.
- Include ARIA: role="alert" and aria-live="polite" on the banner container.
- Use oklch() for all colors (var(--accent), var(--text), var(--muted), var(--card), var(--card-border)).
- Position: fixed at bottom center with max-width constraint.
- Include focus-visible styles for keyboard navigation.

Framework variant (pick one):
A) Vanilla HTML + CSS only — banner dismisses via form action or link to settings page.
B) React component — accept `onAccept`, `onDecline`, `onSettings` callbacks; include localStorage persistence for consent state.

Edge cases to handle:
- Disabled state: add .disabled class with opacity: 0.5 and cursor: not-allowed for buttons.
- Mobile: ensure banner is full-width on screens < 480px with padding adjustments.
- No JS: provide a noscript fallback where banner links to a static consent settings page.

Return HTML + CSS (or TSX + CSS module) with clear separation of concerns.

Why this matters in 2026

Cookie consent is no longer optional — it's a legal requirement across most jurisdictions. While JavaScript handles the persistence, the banner itself can be pure HTML and CSS. This means faster initial paint, better SEO (search engines can read the content), and graceful degradation when JS fails. Modern CSS provides all the tools: position: fixed, flexbox for layout, and var(--*) tokens for theming.

The logic

The banner uses position: fixed; bottom: 1.5rem; to anchor to the viewport bottom. transform: translateX(-50%) centers it horizontally. The role="alert" and aria-live="polite" attributes ensure screen readers announce the banner when it appears. Buttons use semantic <button> elements with hover states driven by :hover and keyboard focus via :focus-visible.

Dismissal logic (adding a .hidden class) can be handled by a tiny script or a form action that reloads the page with a URL parameter to hide the banner via :has() or a body class.

Accessibility & performance

Focus management is critical: when the banner appears, move focus to the first actionable button. When dismissed, return focus to a logical point in the page. Use aria-live="polite" to announce without interrupting. For performance, keep the banner lightweight — no external fonts or images. The @media (prefers-reduced-motion: reduce) query removes any animations for users who prefer less motion.

Tip: Store consent state in localStorage and check it on page load to prevent showing the banner to users who already consented.