From 34de2769f678622f141c093d482c3b6acd52f4b6 Mon Sep 17 00:00:00 2001 From: Tushar <80577646+TusharThakur04@users.noreply.github.com> Date: Wed, 15 Apr 2026 23:38:28 +0530 Subject: [PATCH] fix: resolve flash of unstyled content in dark mode --- src/generators/web/generate.mjs | 10 ++++++- src/generators/web/template.html | 8 ++++-- src/generators/web/ui/theme-script.mjs | 36 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 src/generators/web/ui/theme-script.mjs diff --git a/src/generators/web/generate.mjs b/src/generators/web/generate.mjs index fab595e0..8390ac02 100644 --- a/src/generators/web/generate.mjs +++ b/src/generators/web/generate.mjs @@ -15,7 +15,15 @@ import { writeFile } from '../../utils/file.mjs'; export async function generate(input) { const config = getConfig('web'); - const template = await readFile(config.templatePath, 'utf-8'); + let template = await readFile(config.templatePath, 'utf-8'); + + // Read the theme script from UI folder + const themeScriptPath = join(import.meta.dirname, 'ui', 'theme-script.mjs'); + const themeScriptContent = await readFile(themeScriptPath, 'utf-8'); + + // Wrap theme script in script tag and replace the placeholder + const inlinedThemeScript = ``; + template = template.replace('${themeScript}', inlinedThemeScript); // Process all entries: convert JSX to HTML/CSS/JS const { results, css, chunks } = await processJSXEntries(input, template); diff --git a/src/generators/web/template.html b/src/generators/web/template.html index fe6a5823..87d85ef9 100644 --- a/src/generators/web/template.html +++ b/src/generators/web/template.html @@ -18,8 +18,12 @@ - - + + ${themeScript} diff --git a/src/generators/web/ui/theme-script.mjs b/src/generators/web/ui/theme-script.mjs new file mode 100644 index 00000000..26f73957 --- /dev/null +++ b/src/generators/web/ui/theme-script.mjs @@ -0,0 +1,36 @@ +'use strict'; + +/** + * This script is designed to be inlined in the of the HTML template. + * It must execute BEFORE the body renders to prevent a Flash of Unstyled Content (FOUC). + */ +(function initializeTheme() { + const THEME_STORAGE_KEY = 'theme'; + const THEME_DATA_ATTRIBUTE = 'data-theme'; + const DARK_QUERY = '(prefers-color-scheme: dark)'; + + // 1. Retrieve the user's preference from localStorage + const savedUserPreference = localStorage.getItem(THEME_STORAGE_KEY); + + // 2. Determine if the system/browser is currently set to dark mode + const systemSupportsDarkMode = window.matchMedia(DARK_QUERY).matches; + + /** + * 3. Logic to determine if 'dark' should be applied: + * - User explicitly saved 'dark' + * - User set preference to 'system' AND system is dark + * - No preference exists yet AND system is dark + */ + const shouldApplyDark = + savedUserPreference === 'dark' || + (savedUserPreference === 'system' && systemSupportsDarkMode) || + (!savedUserPreference && systemSupportsDarkMode); + + const themeToApply = shouldApplyDark ? 'dark' : 'light'; + + // 4. Apply the theme attribute to the document element () + document.documentElement.setAttribute(THEME_DATA_ATTRIBUTE, themeToApply); + + // 5. Set color-scheme to ensure browser UI (scrollbars, etc.) matches the theme + document.documentElement.style.colorScheme = themeToApply; +})();