DEV Community

Cover image for I Built a Cookie Banner That Makes It Technically Possible to Reject Cookies
Dmitry Bondarchuk
Dmitry Bondarchuk

Posted on

I Built a Cookie Banner That Makes It Technically Possible to Reject Cookies

April Fools Challenge Submission ☕️🤡

This is a submission for the DEV April Fools Challenge

What I Built

A React component library that faithfully recreates the experience of trying to reject cookies on a modern enterprise website.

By which I mean: you can reject. Technically. Eventually. After a brief sequence of clarifications.

The package is called react-consent-chaos. It ships a ConsentManagerFromHell modal with three hellMode settings ("polite", "pushy", "comically-evil"), three rejectDifficulty levels ("annoying", "absurd", "nightmare"), and a prop called allowRejectEventually whose false case is described in the README as: rejection remains aspirational.

I want to be very clear that I wrote that documentation willingly.

The default company name is "Consent Dynamics". The default vendor count is 1,847. Both are configurable. Neither number has been audited.

Demo

Here is what happens when a user opens the modal on hellMode="pushy":

Initial consent modal

The badge says "Partner-Aligned". The accept button says "Accept all and continue". The reject button says "Reject optional cookies" and underneath that, in smaller text: "Step 1 / 5".

Step 1 of 5.

The user clicks it.

Reject flow escalation

The button now says "Confirm reduced experience".

The status box updates to: "Our partners will be disappointed."

The user, briefly, feels something.

They click again. The button becomes "Acknowledge optimization loss". Then "Continue without joy". Then, on step five — visibly exhausted, through gritted teeth — "Reject anyway".

At this point the modal finally closes and fires the onRejectAll callback with the message: "Fine. We respect your persistence."

This is considered the happy path.


The preferences panel is where the package really earns its name.

Preferences panel

Every consent category has a description written in the voice of a mid-level enterprise product manager who is doing their best:

Category Description
Necessary Required for the continued existence of the button.
Analytics Measures intent, confusion, and funnel sincerity.
Personalization Allows the interface to remember your boundaries and negotiate them.
Legitimate interest A timeless category with exceptional self-esteem.
Mood tracking Detects mild reluctance for premium reassurance.
Productivity optimization Ensures banners arrive during your most fragile focus windows.

"Productivity optimization" is a real consent category in this component. It is off by default. You're welcome.

Event log

Code

Repository: https://github.com/ubcent/react-consent-chaos

npm install react-consent-chaos
Enter fullscreen mode Exit fullscreen mode

Basic usage

import { ConsentManagerFromHell } from "react-consent-chaos";
import "react-consent-chaos/styles.css";

<ConsentManagerFromHell
  open={open}
  onOpenChange={setOpen}
  companyName="Synergy Harvest"
  hellMode="pushy"
  rejectDifficulty="absurd"
  allowRejectEventually
  onAcceptAll={() => console.log("Excellent. Your journey has been optimized.")}
  onRejectAll={() => console.log("Fine. We respect your persistence.")}
  onSavePreferences={handleSavePreferences}
/>
Enter fullscreen mode Exit fullscreen mode

The reject button in action

The ConsentStepButton walks users through a dynamically generated sequence of labels. At difficulty="nightmare" and mode="comically-evil", the full sequence is:

  1. Reject optional cookies
  2. Confirm reduced experience
  3. Acknowledge optimization loss
  4. Continue without joy
  5. Reject anyway
  6. Decline enhanced destiny
  7. Deny data to the revenue temple

Step 7 of 7 is "Deny data to the revenue temple".

I am not sure I can defend this. I am also not taking it out.

The quiet part, out loud

When a user clicks the reject button before they've completed enough steps, two things happen in the source code:

console.warn("user attempted informed choice")
console.warn("consent friction increased")
Enter fullscreen mode Exit fullscreen mode

These are real lines. In the production bundle. Labeled as warnings.

I briefly considered labeling them console.error. I chose not to because I have some remaining sense of proportion.

The preferences panel also has a feature

getManipulatedPreferences runs silently on every save. In pushy mode, it re-enables legitimateInterest regardless of what the toggle says. In comically-evil mode, it also re-enables advertising and partnerSharing.

The function is not hidden. It is named getManipulatedPreferences. It is called inside handleSavePreferences. Any developer who reads the file will find it immediately.

This is not obfuscation. It is transparency with a very specific energy.

The hook, for enthusiasts

const escalation = useConsentEscalation({
  difficulty: "nightmare",
  mode: "comically-evil",
  allowRejectEventually: true,
});

// escalation.statusMessage →
//   "Compliance theater is nearing completion."
//   "Revenue sadness has been acknowledged."
//   "Your defiance has entered the final audit lane."
//   "Fine. We respect your persistence."  ← only on the last step
Enter fullscreen mode Exit fullscreen mode

useConsentEscalation is exported separately for developers who want to implement their own rejection experience without using the full modal. It returns canReject, advanceRejectFlow(), resetRejectFlow(), and a rotating statusMessage that cycles through mode-appropriate passive aggression until the user has exhausted their allocation of steps.

After that: "Fine. We respect your persistence."

That's it. That's the only acknowledgment available. There is no version of this component that says "sure, no problem."

How I Built It

The package is TypeScript-first, bundles to ESM + CJS via tsup, and ships styles as a separate import. No runtime dependencies beyond React 18+.

The hellMode prop controls copy across the entire modal. In comically-evil mode the title changes to "Universal Consent Acceleration Layer", the badge becomes "Legally Adjacent", the accept button becomes "Excellent, optimize me", and the manage preferences button becomes "Audit the damage".

In polite mode — the tamest setting — the helper text reads:

Your privacy matters deeply to us within commercially reasonable limits.

That's the polite version.

The preferences panel heading in comically-evil mode is "Manual resistance configuration". The component also tracks how many times the user has attempted to save preferences (saveCount) and displays it in the UI, because if you're going to be hostile you should at least be transparent about it.

The footer legalese reads:

Necessary cookies are mandatory, spiritually and technically.
Optional categories may remain enabled where enterprise momentum requires.

Both lines are in the real component. Both survived every review I gave them.

The entire thing is accessible: keyboard navigation works, ARIA roles are set, focus is managed on open and panel change, and the status message during the reject flow is wrapped in aria-live="polite" so screen reader users receive every update in real time.

"Our partners will be disappointed." — delivered accessibly, to everyone.

Prize Category

Anti-Value Proposition

The value of this package is entirely negative and precisely calibrated. Setting allowRejectEventually={false} renders a modal where rejection is permanently queued. The progress bar advances. The steps are counted. The button labels grow more resigned with each click. Nothing ever resolves.

The README describes this as: rejection remains aspirational.

I am genuinely proud of that sentence and also slightly concerned about what it says about me.

Creativity

The original insight is that dark patterns are not hidden — they're just undocumented. react-consent-chaos documents all of them. getManipulatedPreferences is a named export. The console.warn lines are readable in any debugger. allowRejectEventually={false} is a prop you pass on purpose.

The joke is that naming the manipulation doesn't make it less manipulative. It just makes it honest manipulation. Which is somehow worse.

Also: the nightmare difficulty allows up to seven steps, and if you set rejectStepsBeforeSuccess to a number beyond the label pool, the fallback label is:

Continue with regrettable self-determination N

where N is the step number. This was not planned. It emerged naturally from the implementation and I kept it because it felt right.

Technical Execution

Real library, real build pipeline, real types, real accessibility, real hook API. The ConsentStepButton and useConsentEscalation are individually exported for anyone who wants to compose their own dark-pattern UI from primitives. The component handles controlled and uncontrolled state. The overlay is non-closable by default. Escape is gated behind overlayClosable. Focus returns correctly.

It is a well-engineered component whose entire purpose is to demonstrate how much engineering goes into making people feel bad about wanting privacy.

Writing Quality

Every string in this codebase was written as if it would appear in an actual product and reviewed by an actual legal team in an actual company that has lost the thread. "Delight generation may be reduced." "Your independence is being carefully reviewed." "A timeless category with exceptional self-esteem."

These are not captions. They are labels. They render in the UI. They are wrapped in ARIA attributes and shipped in a bundle.

The demo's event log initializes with the entry: "Awaiting a fresh compliance event."

I think about that line sometimes.

Top comments (0)