TL;DR: Forcing a pure dark mode UI harms reading comprehension for users with astigmatism. By
utilizing CSS light-dark(), View Transitions, and a render-blocking inline script, you can build
a highly performant, zero-JS light/dark theme toggle without Flash of Unstyled Content (FOUC).
Table of Contents
When I first launched this portfolio, the design philosophy was simple: minimalist, typography-focused, and unapologetically dark.
As an AI-accelerated full-stack developer, a deep, museum-like dark mode felt like the perfect backdrop for my work. After all, among tech-focused audiences, nearly 81.9% of developers prefer dark mode interfaces.
But recently, I found myself questioning this rigid aesthetic. As I began publishing more long-form devlogs and deep-dives into software architecture, I had to ask myself: is a forced dark mode actually serving my readers, or just my brand?
This question sent me down a rabbit hole of visual ergonomics, user experience data, and modern CSS architecture, ultimately inspiring me to build a native, zero-JS light/dark mode toggle.
Here is a look into my decision-making process and how I engineered a solution that respects both user accessibility and absolute performance.
The Science of Screen Reading: Why Dark Mode Isn’t Perfect
I love dark mode.
It reduces overall screen luminance, which minimizes eye strain during late-night coding sessions and helps prevent the melatonin suppression that disrupts circadian rhythms.
However, when it comes to reading dense architectural documentation, pure dark mode has a hidden biological cost.
When you look at a dark screen, your pupils dilate to let in more light. For the roughly one-third of the population with some degree of astigmatism, this dilation triggers the “halation effect.”
The halation effect is a visual distortion where light text appears to bleed or blur into the dark background.
Conversely, light mode (positive polarity) causes the pupils to constrict. This constriction increases your eyes’ depth of field and sharpens focus.
Studies show this objectively increases reading speed and comprehension for dense text blocks.
I realized that by forcing a dark theme, I was subtly fatiguing my readers just when they needed the most focus.
The Audience Divide: Recruiters vs. Developers
My website caters to two very distinct demographics operating in completely different environments:
Fellow Developers: Browsing at 2:00 AM for inspiration or open-source solutions, where a bright white screen would be blinding.
Recruiters and Engineering Managers: Reviewing my portfolio at 2:00 PM in brightly lit corporate offices, where dark mode creates terrible screen glare.
I initially thought about just relying on the OS-level prefers-color-scheme setting. However, many power users keep their operating systems in global dark mode to save OLED battery life, but still prefer reading long articles in light mode.
Providing a manual toggle delegates environmental management back to the user, offering true autonomy. A well-executed theme option signals a profound commitment to user needs—an essential trait for any senior architect.
The Technical Execution: Zero-FOUC and Native CSS
The biggest hurdle was my own architectural standard: the site had to maintain its “zero-JS load time” philosophy.
I refused to implement a bloated React Context provider that would cause a jarring Flash of Unstyled Content (FOUC) upon loading.
To align with the “speed of AI” mantra, I utilized the most robust CSS patterns available in 2026.
Semantic CSS Functions
Instead of massive media query blocks, I adopted the native light-dark() CSS function.
Paired with color-scheme: light dark; on the :root, this drastically reduces CSS payload and natively handles system defaults.
/* Using the modern light-dark() CSS function */
:root {
color-scheme: light dark;
/* No media queries needed! */
--bg-color: light-dark(#ffffff, #0a0a0a);
--text-color: light-dark(#111827, #f3f4f6);
--border-color: light-dark(#e5e7eb, #1f2937);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
This ensures that the browser intrinsically understands how to switch colors based on the data-theme or system preference.
Eliminating the FOUC (Flash of Unstyled Content)
To ensure a flawless initial render in Astro, I injected a render-blocking <script is:inline> directly into the document <head>.
This tiny snippet synchronously checks localStorage or system preferences and applies a data-theme attribute before the browser even paints the first pixel.
<!-- Inject in <head> to prevent FOUC -->
<script is:inline>
const getTheme = () => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
};
const theme = getTheme();
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
</script>
Because it executes before DOM parsing completes, there is zero flicker. The page simply loads in the correct theme instantly.
The View Transitions API
A toggle shouldn’t just be functional; it should be an experience.
I leveraged the native View Transitions API to create a seamless, hardware-accelerated circular reveal animation when switching themes.
It requires virtually zero heavy JavaScript libraries and perfectly encapsulates the polished, high-speed engineering standard I strive for.
// ThemeToggle.jsx (Island)
const toggleTheme = (e) => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
if (!document.startViewTransition) {
applyTheme(newTheme);
return;
}
// Calculate circular reveal origin
const x = e?.clientX ?? innerWidth / 2;
const y = e?.clientY ?? innerHeight / 2;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
const transition = document.startViewTransition(() => {
applyTheme(newTheme);
});
transition.ready.then(() => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: newTheme === 'dark' ? [...clipPath].reverse() : clipPath,
},
{
duration: 400,
easing: 'ease-out',
pseudoElement:
newTheme === 'dark' ? '::view-transition-old(root)' : '::view-transition-new(root)',
},
);
});
};
This creates a stunning ripple effect originating from the user’s click, all handled smoothly by the browser engine.
Final Thoughts
Integrating a light mode into a dark-native portfolio wasn’t a compromise of my brand identity; it was an evolution of it.
Engineering at the speed of AI isn’t just about shipping code quickly.
It’s about building resilient, adaptable interfaces that meet users exactly where they are.
By handing control back to the reader, this portfolio now functions as a visually dramatic gallery in the dark, and a crisp, authoritative whitepaper in the light.