Thesis: When script-src is 'self', an inline theme-boot script never runs. First paint stays unthemed unless theme-dark (and color-scheme) already live on the static <html>.
What we shipped first
We shipped a client app shell under Amplify custom headers with script-src 'self' — no 'unsafe-inline'. The index still carried an inline theme-boot script that set theme-dark and color-scheme before paint. The browser blocked it. The shell looked unthemed on first paint even though the CSS and a later theme module were fine.
The wrong fix would have been to reopen 'unsafe-inline'. We removed the inline boot instead and put forest on the static HTML.
The working shape
Keep the CSP strict. Put theme-dark on <html> in the markup (and color-scheme: dark). Runtime theme code may still toggle later from an external file. First paint must not depend on an inline script.
<!-- Working shape: theme class is static HTML. No inline script. -->
<html class="h-full theme-dark" style="color-scheme: dark">
<!-- CSP: script-src 'self' — external theme.js may still toggle later -->
</html>
<!-- Wrong green: inline boot under a strict CSP never runs -->
<!-- <script>document.documentElement.classList.add("theme-dark")</script> -->
Checklist
- Confirm Amplify (or edge) CSP keeps script-src 'self' — no inline exception for theme boot.
- Remove any inline theme-boot <script> from the app index.
- Put theme-dark and dark color-scheme on the static <html>.
- Hard-reload once; first paint should already be themed with no CSP inline-script console error for boot.
Try the lab
Observe the unthemed paint, name why the inline boot never ran, choose the static-theme fix, then prove. Commands: observe · why · fix · prove.
Full page: Open this lab · All labs
Related: late font faces must be optional. How we deliver: methodology · readiness.
Engineering commentary only — not audit, legal, or certification advice.