Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
www
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PLN
www
Commits
06720e6d
Commit
06720e6d
authored
Nov 01, 2026
by
PLN (Algolia)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
theme: pin the resolved theme before paint; pill label waits for mount
parent
72e60913
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
19 additions
and
9 deletions
+19
-9
layout.tsx
app/layout.tsx
+7
-4
ThemeSwitcher.jsx
components/ThemeSwitcher.jsx
+12
-5
No files found.
app/layout.tsx
View file @
06720e6d
...
...
@@ -29,11 +29,14 @@ export const metadata = {
/**
* Resolves the theme before first paint: stored choice wins, else system.
* Keeps the HTML attribute semantics of styles/tokens.css — "dark" set
* explicitly, "light" set explicitly (to opt out of the media query),
* nothing when the reader has no JS (the media query covers them).
* Always pins the resolved value — including "light", which must be set
* explicitly to opt out of the prefers-color-scheme fallback in
* styles/tokens.css (leaving it unpinned paints dark until hydration on
* dark-system machines whose reader chose light). Nothing is set when the
* reader has no JS… except there is no "when": this script only runs with
* JS. The no-JS story is the media query alone, untouched.
*/
const
themeBootstrap
=
`(function(){try{var t=localStorage.getItem('pln:theme')
;if(t!=='light'&&(t==='dark'||window.matchMedia('(prefers-color-scheme: dark)').matches)){document.documentElement.dataset.theme='dark'}
}catch(e){}})()`
;
const
themeBootstrap
=
`(function(){try{var t=localStorage.getItem('pln:theme')
||'auto';document.documentElement.dataset.theme=t==='auto'?(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'):t
}catch(e){}})()`
;
export
default
function
RootLayout
({
children
}:
{
children
:
React
.
ReactNode
})
{
return
(
...
...
components/ThemeSwitcher.jsx
View file @
06720e6d
...
...
@@ -20,14 +20,17 @@ const LABELS = {
* this component only manages the cycle and the persistence.
*/
export
default
function
ThemeSwitcher
()
{
const
[
mode
,
setMode
]
=
useState
(
'auto'
);
// Null until mounted: SSR would paint the label "auto" even for a reader
// whose stored mode is light/dark — the glimpse this pill used to flash.
// An empty pill for a frame beats a wrong label.
const
[
mode
,
setMode
]
=
useState
(
null
);
useEffect
(()
=>
{
try
{
const
stored
=
window
.
localStorage
.
getItem
(
KEY
);
if
(
MODES
.
includes
(
stored
))
setMode
(
stored
);
setMode
(
MODES
.
includes
(
stored
)
?
stored
:
'auto'
);
}
catch
{
// storage refused — the pill still cycles, unaided
setMode
(
'auto'
);
// storage refused — the pill still cycles, unaided
}
// Follow the system while in auto.
const
media
=
window
.
matchMedia
(
'(prefers-color-scheme: dark)'
);
...
...
@@ -68,13 +71,17 @@ export default function ThemeSwitcher() {
type=
"button"
onClick=
{
cycle
}
className=
{
styles
.
pill
}
aria
-
label=
{
LABELS
[
mode
]
}
title=
{
LABELS
[
mode
]
}
aria
-
label=
{
LABELS
[
mode
]
??
'Thème'
}
title=
{
LABELS
[
mode
]
??
'Thème'
}
>
{
mode
&&
(
<>
<
span
aria
-
hidden=
"true"
className=
{
styles
.
icon
}
>
{
ICONS
[
mode
]
}
</
span
>
<
span
className=
{
styles
.
mode
}
>
{
mode
}
</
span
>
</>
)
}
</
button
>
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment