Skip to content

fix: resolve flash of unstyled content in dark mode#766

Open
TusharThakur04 wants to merge 1 commit intonodejs:mainfrom
TusharThakur04:fix/dark-mode-FOUC
Open

fix: resolve flash of unstyled content in dark mode#766
TusharThakur04 wants to merge 1 commit intonodejs:mainfrom
TusharThakur04:fix/dark-mode-FOUC

Conversation

@TusharThakur04
Copy link
Copy Markdown
Contributor

Description

This PR resolves the FOUC by resolving system to dark or light using matchMedia.
Earlier system was directly being applied as set-theme = system

Validation

before :

Screencast.from.2026-04-13.19-26-34.webm

after :

Screencast.from.2026-04-13.19-24-03.webm

Related Issues

n/a

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run node --run test and all tests passed.
  • I have check code formatting with node --run format & node --run lint.
  • I've covered new added functionality with unit tests if necessary.

@TusharThakur04 TusharThakur04 requested a review from a team as a code owner April 13, 2026 14:23
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 13, 2026

PR Summary

Low Risk
Low risk, localized change to the static HTML template/theme bootstrap logic; main risk is incorrect theme selection or unexpected behavior in browsers with restricted localStorage/matchMedia.

Overview
Fixes dark-mode Flash of Unstyled Content by replacing the one-liner theme bootstrap in template.html with an inlined, shared themeScript that resolves saved preference (including system) to an actual dark/light theme before first paint.

The web generator now imports and injects this themeScript during HTML rendering (processing.mjs), centralizing early theme application and ensuring data-theme and colorScheme are set consistently.

Reviewed by Cursor Bugbot for commit 501c20b. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api-docs-tooling Ready Ready Preview Apr 18, 2026 2:28pm

Request Review

Comment thread src/generators/web/template.html Outdated
Comment thread src/generators/web/template.html Outdated

<!-- Apply theme before paint to avoid Flash of Unstyled Content -->
<script>document.documentElement.setAttribute("data-theme", document.documentElement.style.colorScheme = localStorage.getItem("theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"));</script>
<script>(function(){var p=localStorage.getItem("theme");var t=(!p||p==="system")?(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):p;document.documentElement.setAttribute("data-theme",t);document.documentElement.style.colorScheme=t;})();</script>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make this code block more readable for developers who want to contribute or review it. Instead of writing it as a single line, using more descriptive variable names shouldn't significantly impact the file size. @nodejs/web-infra WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case this script source should be in its own file and then inlined via a template string {{}}

@avivkeller
Copy link
Copy Markdown
Member

This seems highly complex

@TusharThakur04
Copy link
Copy Markdown
Contributor Author

the {$themeScript} literal is being replaced by newly created themeScript by generate.mjs

it is done so that the script is available before rendering the html and there is no latency which could have happened if browser makes request for the themeScript.mjs if we use src inside the script tag

Comment thread src/generators/web/generate.mjs Outdated

// Wrap theme script in script tag and replace the placeholder
const inlinedThemeScript = `<script>${themeScriptContent}</script>`;
template = template.replace('${themeScript}', inlinedThemeScript);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use .replace(), I believe that @avivkeller implemented a system to automatically evaluate such template variables?

Comment thread src/generators/web/template.html Outdated
It injects an inline script that initializes the theme (light/dark)
before the page renders to avoid Flash of Unstyled Content (FOUC).
-->
${themeScript}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
${themeScript}
<script type="javascript">${themeScript}</script>

Comment thread src/generators/web/template.html Outdated

<!-- Apply theme before paint to avoid Flash of Unstyled Content -->
<script>document.documentElement.setAttribute("data-theme", document.documentElement.style.colorScheme = localStorage.getItem("theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"));</script>
<!--
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this comment.

Comment thread src/generators/web/generate.mjs Outdated

// Read the theme script from UI folder
const themeScriptPath = join(import.meta.dirname, 'ui', 'theme-script.mjs');
const themeScriptContent = await readFile(themeScriptPath, 'utf-8');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't ue fs the idea is to use our rolldown pipeline for this script, so it gets properly minified and transformed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise the file doesn't get minified, which is the idea behind separating src<>final result.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 18, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 77.95%. Comparing base (3d00fa8) to head (501c20b).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
src/generators/web/utils/processing.mjs 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #766      +/-   ##
==========================================
- Coverage   78.43%   77.95%   -0.49%     
==========================================
  Files         157      159       +2     
  Lines       13962    14057      +95     
  Branches     1152     1152              
==========================================
+ Hits        10951    10958       +7     
- Misses       3006     3094      +88     
  Partials        5        5              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/generators/web/utils/processing.mjs Outdated
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3c1f1be. Configure here.

Comment thread src/generators/web/ui/theme-script.mjs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants