Disable dark patterns: fake urgency, sneaky checkboxes
"Only 2 left in stock." "12 people are viewing this right now." A countdown timer that resets every time you reload. A pre-checked box adding insurance you never asked for. These are dark patterns — interface tricks designed to rush and trick you. This article shows how to neutralize the common ones with CSS and JavaScript so you can shop with a clear head.
What counts as a dark pattern
A dark pattern is a UI choice that benefits the business at your expense by exploiting how attention and defaults work. The ones worth disarming on a shopping page:
- Fake urgency — countdown timers, "sale ends in 04:59" that restarts on reload.
- Fake scarcity — "only 2 left", "selling fast", numbers that have no connection to real inventory.
- Fake social proof — "12 people are viewing this", random and unverifiable.
- Sneak-into-basket — pre-checked add-ons: insurance, donations, express shipping.
- Confirmshaming — the decline link worded to shame you: "No thanks, I don't like saving money".
You cannot stop a site from being designed this way, but you can edit how it renders in your browser.
Spotting a fake countdown
A real deadline is the same for everyone and survives a reload. A fake one resets. Quick test: note the timer, reload the page, look again. If it jumped back to "05:00", it is theatre. Open DevTools, Elements panel, and find the element whose text ticks down — note its class.
Hiding fake-urgency widgets with CSS
The fastest fix is to make the pressure widgets disappear entirely. They are almost always cleanly separated, named-for-what-they-are elements:
/* Fake urgency, scarcity and social-proof widgets */
.countdown-timer,
.sale-timer,
.urgency-banner,
.stock-warning,
.low-stock,
.viewing-now,
.recently-viewed-count,
[class*="countdown" i],
[class*="urgency" i],
[class*="scarcity" i],
[class*="viewers" i],
[class*="people-viewing" i] {
display: none !important;
}
As always, the i flag makes attribute matching case-insensitive. Start with this block; it clears the visible noise on most retail pages instantly.
Freezing a countdown instead of hiding it
Sometimes you want to see that a sale exists but not be hurried by a ticking clock. Freeze it: stop the script that updates it. The blunt, reliable approach is to neutralize the timer functions before the page's script starts, at document_start:
// Freeze countdowns: defang the timers the page uses to tick
const realSetInterval = window.setInterval;
window.setInterval = function (fn, delay, ...args) {
// Block fast-ticking timers (countdowns update ~every second)
if (delay && delay <= 1000) {
console.log('[dark-patterns] blocked a 1s interval');
return -1; // a harmless fake id
}
return realSetInterval.call(this, fn, delay, ...args);
};
This is deliberately broad — it stops every sub-second interval, which on a product page is almost always a countdown. If it freezes something you need, narrow it by checking fn.toString() for keywords like countdown before blocking.
A gentler alternative: let the timer run but pin its displayed text once, then stop updates to that node:
// Pin the countdown text and stop it changing
const timer = document.querySelector('[class*="countdown" i]');
if (timer) {
const frozen = timer.textContent;
new MutationObserver(() => {
if (timer.textContent !== frozen) timer.textContent = frozen;
}).observe(timer, { childList: true, subtree: true, characterData: true });
}
Un-checking sneaky add-on boxes
The most expensive dark pattern is the pre-checked checkbox: shipping insurance, a "round up for charity", a recurring membership. It is checked by default and worded so you skim past it. Un-check them with JavaScript after the page loads:
// Un-tick pre-checked add-on checkboxes
function unchargeMe() {
document.querySelectorAll('input[type="checkbox"]:checked')
.forEach(box => {
const label = (box.closest('label')?.textContent
|| box.parentElement?.textContent || '').toLowerCase();
// Only touch boxes that smell like a paid add-on
if (/insurance|protection|warranty|donation|round up|express|priority|membership|subscribe/.test(label)) {
box.checked = false;
// Fire events so the page recalculates the total
box.dispatchEvent(new Event('change', { bubbles: true }));
box.dispatchEvent(new Event('input', { bubbles: true }));
console.log('[dark-patterns] un-checked:', label.trim().slice(0, 60));
}
});
}
unchargeMe();
// Re-run when the cart re-renders
new MutationObserver(unchargeMe)
.observe(document.body, { childList: true, subtree: true });
The change and input events matter — without them the page total may still include the add-on even though the box looks unchecked. Always verify the price updated.
Defusing confirmshaming wording
You cannot easily rewrite text, but you can stop confirmshaming from doing its job: make the decline option as visible and neutral as the accept option. If a site styles "No thanks" as tiny gray text, restyle it:
/* Make the decline link a normal, readable button */
.modal a[class*="decline" i],
.modal button[class*="dismiss" i],
[class*="confirm-shame" i] {
font-size: 1rem !important;
color: inherit !important;
text-decoration: underline !important;
opacity: 1 !important;
}
Scoping it with JustZix
Some of these rules are aggressive — blocking sub-second intervals could affect a live-updating widget you actually want. So scope them. Create a folder called "Anti dark-patterns" and add one rule per shopping site you use, each with a tight URL pattern. Toggle the whole folder off when you are not shopping. You get a calmer checkout without side effects elsewhere.
See also
- Remove soft paywalls — the same overlay-removal skills.
- Build a price and stock watcher — track real prices instead of fake urgency.
- Ready-to-paste snippets in the Examples section.
Shopping should be a decision, not a reaction. Install JustZix, paste the CSS block above, and watch the manufactured pressure vanish from every product page you visit.
Rate this post
No ratings yet — be the first.