Fixing the Firefox flash, nav overhaul, and Bootstrap colour modes

Remember in my last post when I was complaining about the Firefox flash, a blink whenever you navigate between pages? Well, I finally fixed it. And in the process I ripped out the hacked together dark mode and implemented proper Bootstrap 5.3 colour modes with a light/dark/auto toggle. Here's how it went.

The Firefox flash

I spent ages trying every CSS loading trick in the book: preloading, moving scripts around, inline critical CSS, you name it. Nothing worked. The flash would still happen on Firefox but not on Chromium, which made me think it was a Firefox rendering issue.

Turns out it was bfcache (back-forward cache). Firefox caches a snapshot of the page in memory when you navigate away, and when you come back it restores that snapshot before running any JavaScript. The dark mode class would be missing from the restored snapshot, flash the light theme, then JavaScript would fix it. The fix is just one line:

window.addEventListener('unload', () => {});

This 42-byte listener tells Firefox the page has an unload handler, which apparently stops whatever was causing the flash - whether it's bfcache or something else, the flash simply stopped. I'm not entirely sure why this works on forward navigation too, but it does.

Mobile Firefox

On Android Firefox the same unload trick doesn't help at all - the flash still happens on every page load. I don't fully understand why the same fix behaves differently on mobile.

I tried inlining critical CSS, setting color-scheme hints, and various meta tags. The persisting flash could be the browser's own navigation transition animation - a browser-chrome behaviour that can't be fully overridden from the page.

Bootstrap 5.3 colour modes

My old dark mode was a mess of @media (prefers-color-scheme: dark) rules that only worked if your system was set to dark mode. No toggle or persistence, and it wasn't in line with Bootstrap's own dark mode expectations.

Bootstrap 5.3 has a built-in colour mode system based on data-bs-theme="light|dark" on the <html> element. The key insight is that you need to set this attribute before the first paint, otherwise you get the same flash problem. The solution is an inline <script> right at the top of <head> that reads from localStorage (and falls back to the system preference via prefers-color-scheme), then sets data-bs-theme synchronously.

const getStoredTheme = () => localStorage.getItem('theme');
const theme = getStoredTheme() || 'auto'; const resolved = theme ===
'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ?
'dark' : 'light')
  : theme; document.documentElement.setAttribute('data-bs-theme',
resolved);

By the time the browser even starts painting, the theme is already set. No flash, flicker or javascript waiting.

The theme toggle

The toggle is a bootstrap dropdown with three options: light, dark, and auto. Clicking any of them saves the preference to localStorage and updates data-bs-theme on <html>.

Placing it in the navbar was trickier than expected. On mobile it needs to be outside the hamburger collapse (always visible, left of the hamburger icon). On desktop it needs to be inside the collapse, to the left of the search bar. Bootstrap doesn't have a clean way to have an element in two places at once, so i ended up with two copies of the dropdown using responsive visibility:

<!-- mobile: outside collapse, hidden on lg+ -->
<div class="dropdown d-lg-none"> <button class="bd-theme
nav-link dropdown-toggle px-2" ...> <i class="bi
bi-circle-half"></i> </button> ...  </div>

<!-- desktop: inside collapse, hidden below lg --> <div
class="dropdown d-none d-lg-block"> <button class="bd-theme
nav-link dropdown-toggle px-2" ...> <i class="bi
bi-circle-half"></i> </button> ...
</div>

The JavaScript handles both buttons via document.querySelectorAll('.bd-theme') and updates the icon and aria-label on both when the theme changes.

Navbar and breadcrumbs

I also overhauled the navigation. The page trail (breadcrumbs) used to be in the top bar and the navigation links in the sidebar. I wrote a small Ikiwiki plugin (navlinks) that takes a configured list of top-level pages and passes them to the template as NAVLOOP, so the navbar is a proper list of links. The page trail got moved to a Bootstrap breadcrumb row below the header. The sidebar was removed (sidebar.mdwn deleted) but the blog page's sidebar was kept for its calendar and links.

Green links

My original CSS had a { color: #008000; } globally, which wasn't a problem before but looked terrible after I did the Bootstrap changes and the navbar turned into a Christmas tree. I kept the global rule since Bootstrap's own .navbar-nav .nav-link selector overrides it with higher specificity — so the navbar fixed itself just by giving it proper Bootstrap navbar classes. I left the light mode global green for all links, and changed the dark mode to a less eye-searing #11D116 scoped to the content area:

a { color: #008000; }
[data-bs-theme="dark"] #content a:not(.btn):not(.feedbutton) { color: #11D116; }

Calendar and sidebar styling

The sidebar was kept for the blog calendar and archive/tag links, but it needed the same dark mode treatment as the content area. Sidebar links (#sidebar a) now get #11D116 in dark mode, matching the content green.

The calendar ← May 2026 → links were a bit overbearing in bright green. In light mode they now match the day-of-week headers (#eceeef) against the dark grey calendar table header. In dark mode they stay green like the rest of the content.

The arrows themselves were rendering differently across browsers. Chromium would show a different glyph than Firefox. I replaced them with Bootstrap Icons (bi-chevron-left/bi-chevron-right) using CSS ::before pseudo-elements, hiding the original HTML entity text with font-size: 0. Firefox also needed an explicit text-decoration: underline on the pseudo-element since it doesn't inherit the link underline the same way Chromium does.

Calendar cells with blog posts and the current day now have a light green background (#d4edda) instead of light blue. In dark mode the highlight is a darker green (#3a6b35) with grey text for legibility.

The popup balloon that shows blog post titles on hover also had a dark mode bug: the text colour was the same as the background, making it invisible. Now it uses #aaa.

Inline `code` in markdown now has its own colour matching the rainbow theme: #7B5E00 (dark goldenrod) in light mode, #ffcc66 (golden yellow) in dark mode. The :not(.hljs) selector keeps it from affecting highlight.js code blocks.

Miscellaneous

  • Pure black (#000000) body background in dark mode instead of Bootstrap's default dark gray
  • Breadcrumb links are #11D116 in dark mode
  • Switched all timestamps to UTC
  • Footer links are #11D116 in dark mode
  • Mobile nav links are in a 3-column grid layout instead of stacking
  • I now load highlight.js on all pages because the size and load time is negligible.

The theme

The theme lives in a git submodule (ikistrap) so if you want to use it for your own website, you can.

Conclusion

The website no longer blinds me with a flash when clicking links on desktop, the navbar is more convenient and better for mobile, the theme toggle works everywhere and remembers my preference, and the code is closer to Bootstrap standards instead of a pile of hacks.