Skip to content

A Sticker on the Wrong Wall: One Brand Mark, Two Files

Adityo Guni Waluyo

A dark pill was a patch, not a fix: a brand mark needs two assets the moment a site ships a real, user-controlled dark mode.

TL;DR

A brand wordmark can't work on both themes as one asset, so two Next.js Image components share one slot, toggled with dark:hidden and dark:block—zero JavaScript. The Tailwind v4 gotcha: dark defaults to prefers-color-scheme, not a class, so the override must respect the manual toggle. Filters and single-file hacks fail; two files keep theme state in one place.

01:01 at night, commit b09c97d just pushed. After refactoring the bottom-of-page anatomy, one thing still bothered me: the brand slot held a single white wordmark sitting on a dark pill background. In dark mode it looked fine. Switch to light mode and it looked absurd, like a sticker stuck on the wrong wall. The contrast died and the brand mark floated there, apologizing for itself.

My working assumption had been lazy but comfortable: the pill is good enough, one asset, done. The reality is less forgiving. A brand mark needs two assets the moment a site ships a real, user-controllable dark mode. Unlike text, an image cannot be recolored by CSS without side effects; each wordmark is drawn for the background it lives on.

Two Images in One Slot

The fix copies what the header already did: two Next.js <Image> components share one slot, sorted by utility classes [6]. The navy wordmark gets dark:hidden, so it only renders in light mode. The white one gets hidden dark:block and appears only when dark mode is on. Zero JavaScript, no resize observers, no source swapping in event handlers.

Both images keep their width and height so the browser reserves the same box regardless of theme, and a sizes hint keeps the srcset pick sensible [6]. Alt text stays descriptive and identical in meaning on both, because a screen reader does not care which theme is active.

Half-measures with the single file got their fifteen minutes too. Lower the white wordmark's opacity in light mode? The letters turn a muddy gray and the anti-aliased edges look dirty on retina screens. Bake both wordmarks into one PNG and shift the crop per theme? The composition is fixed forever: spacing between letters cannot adapt, and the badge we later wanted next to the wordmark turned into a redesign instead of a class change. Two files is the honest version; CSS only decides which one shows.

There is also a consistency argument that has nothing to do with pixels. The header had already solved this exact problem with the swap pattern. Keeping the patch in the bottom slot meant the codebase answered the same question two different ways, and the next developer would eventually copy the wrong one. Deleting the pill made the codebase agree with itself, which matters more than any single pixel.

The Tailwind v4 Gotcha

The part that cost me a few confused minutes: in Tailwind v4, the dark variant defaults to the prefers-color-scheme media query, not a class [7]. That media query reads the theme preference at the OS or browser level [8]. This project overrides the variant to a .dark class on the html element, toggled by a button and persisted in localStorage. Misread that default and the brand mark silently follows the OS while ignoring the user toggle. No error, no warning, exactly the kind of bug that only shows up on a machine whose OS theme mismatches the site.

Two alternatives never made it past the whiteboard. A CSS invert filter flips every color at once, and the gold accent in this brand turns into some alien blue; plain CSS has no tasteful selective invert. A <picture> element with a media query fails for the same reason the default variant would: it reads the OS preference and cannot see the stored toggle.

If your site ships only a system-following dark mode, the calculus changes and a single asset with media queries might genuinely be enough. The two-file pattern earns its keep exactly when a manual toggle exists: the state lives in one place, the classes react to it, and every asset gets a version designed for the background it will actually sit on. Decide per project, but decide consciously.

So the standard in this project is now simple: every brand-asset slot follows the two-file pattern. One small extra image in the bundle is a cheap price compared to fighting user expectations every time someone presses the theme button.

Sources

[6] Next.js: Image Component

[7] Tailwind CSS: Dark mode

[8] MDN: prefers-color-scheme

Related articles