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.