Ship <CookieConsentBanner> in @repo/core-ui — a headless component with a EU-compliant default UI (equal-prominence Reject All / Accept All), granular category toggles, render-prop overrides for branding, __consent_state cookie management for the anonymous pre-signup flow, and useConsent() integration for authenticated state.
Why
Downstream consumers need a drop-in cookie consent banner that satisfies CNIL guidance and EDPB Art. 7 interpretation out of the box (equal visual weight for Reject All / Accept All, no pre-ticked boxes, equal tab order). Building the component as a headless default with render-prop overrides lets consumers brand the visuals without forking the compliance logic. The Storybook story doubles as the human-readable compliance review surface.
Done when
<CookieConsentBanner variant="modal"> renders with Reject All / Accept All as equal-size, equal-weight side-by-side buttons; tab order treats them equally; ARIA labels mirror; focus-trapped inside modal; ESC = "Reject All" (explicit legal choice, not silent dismiss).
<CookieConsentBanner variant="banner"> renders fixed to the bottom of the viewport, full-width.
onConsentChange callback fires with updated UserConsentState.
When <ConsentProvider> is present (authenticated context), the banner reads and writes via useConsent() from @repo/core-consent/react; when absent, the banner manages state via the cookie only (anonymous flow).
Storybook story covers: modal variant, banner variant, render-prop override example, a11y tab-order demo.
axe-core a11y test in Storybook passes (WCAG 2.2 AA color contrast, no violations).
RTL behavioral tests assert: click "Reject All" → onConsentChange fires with all non-essential categories granted: false; toggle analytics → click "Save Selected" → onConsentChange fires with analytics.granted: true; ESC in modal → Reject All semantics; tab order visits Reject All before Accept All.
Modal focus-trap test: focus does not escape the modal while it is open.
pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff all pass.
Storybook story in packages/core-ui/src/cookie-consent-banner/cookie-consent-banner.stories.tsx.
axe-core a11y integration in Storybook + RTL behavioral tests.
Export from packages/core-ui/src/index.ts barrel.
SSR-safe pattern: component is client-only; ship a <CookieConsentBannerLoader> SSR placeholder that dynamic-imports the actual banner client-side (see consent.md, Q3 of grill).