Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions css/elements.css
Original file line number Diff line number Diff line change
Expand Up @@ -1376,11 +1376,32 @@ a.menu-pane-header-production {
display: none;
}

#menu-search-row {
display: flex;
gap: 4px;
margin: 5px 0 0 0;
}

#color-scheme-toggle {
display: flex;
align-items: center;
padding: 0 4px;
color: var(--control-dimmed-foreground-color);
background-color: var(--control-input-background-color);
border: 1px solid var(--control-input-border-color);
cursor: pointer;
}

#color-scheme-toggle:hover {
color: var(--foreground-color);
}

#menu-search-box {
box-sizing: border-box;
display: block;
width: 100%;
margin: 5px 0 0 0;
flex-grow: 1;
min-width: 0;
margin: 0;
font-size: 1em;
padding: 2px;
color: var(--foreground-color);
Expand Down
45 changes: 45 additions & 0 deletions js/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,48 @@ function makeLinkToId(id) {
return path + '#' + id;
}

// The theme is chosen by `prefers-color-scheme` media queries, which script can't override,
// so to show the opposite of the system's scheme we swap `light` and `dark` in those queries.
let systemPrefersDark = matchMedia('(prefers-color-scheme: dark)');
let colorSchemeInverted = false;

// a change to the system's scheme is a new preference, so go back to following it
systemPrefersDark.addEventListener('change', () => {
if (colorSchemeInverted) {
toggleColorScheme();
}
});
Comment on lines +1168 to +1173

@nicolo-ribaudo nicolo-ribaudo Oct 23, 2026 •

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 switch between light and dark mode system-wide in the night (but I rarely look at ecma262 when that happens, so I almost never look at the dark mode version 😅).

It would be better if the states were light/dark/system so that one can either set a preference or choose to follow what the system does, rather than ecma262 forgetting my preference (if I'll set any) every time the change happens.

@nicolo-ribaudo nicolo-ribaudo Oct 23, 2026 •

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.

Or, if you want a two-state toggle, following what @LeaVerou suggests on her blog post: https://lea.verou.me/blog/2026/dark-mode-toggles/

Another common mistake is being overzealous about removing the stored value when the system preference changes, even if the user has explicitly set an override.
This evaluation must only happen at user interaction.

(fwiw Lea, I find your analogy with tap faucets to be quite... leaking. The best faucets are the ones with two separate knobs, one for pressure and one for temperature. The worse ones are those with a single one-axis knob that somehow controls both parameters, and that I've only found in American and Japanese hotels).


function toggleColorScheme() {
colorSchemeInverted = !colorSchemeInverted;
let dark = systemPrefersDark.matches !== colorSchemeInverted;
document.documentElement.style.colorScheme = colorSchemeInverted ? (dark ? 'dark' : 'light') : '';
for (let sheet of document.styleSheets) {
let rules;
try {
rules = sheet.cssRules;
} catch (e) {
// cross-origin stylesheets can't be inspected
continue;
}
invertColorSchemeQueries(rules);
}
}

function invertColorSchemeQueries(rules) {
for (let rule of rules) {
if (rule.media?.mediaText.includes('prefers-color-scheme')) {
rule.media.mediaText = rule.media.mediaText.replace(
/(prefers-color-scheme:\s*)(light|dark)/g,
(_, feature, scheme) => feature + (scheme === 'dark' ? 'light' : 'dark'),
);
}
if (rule.cssRules) {
invertColorSchemeQueries(rule.cssRules);
}
}
}

function doShortcut(e) {
if (!(e.target instanceof HTMLElement)) {
return;
Expand All @@ -1178,6 +1220,8 @@ function doShortcut(e) {
document.documentElement.classList.toggle('show-uc-annotations');
} else if (e.key === 'e') {
document.documentElement.classList.toggle('show-early-exits');
} else if (e.key === 'd') {
toggleColorScheme();
} else if (e.key === '?') {
document.getElementById('shortcuts-help').classList.toggle('active');
}
Expand All @@ -1188,6 +1232,7 @@ function init() {
return;
}
menu = new Menu();
document.getElementById('color-scheme-toggle').addEventListener('click', toggleColorScheme);
let $container = document.getElementById('spec-container');
$container.addEventListener(
'mouseover',
Expand Down
5 changes: 4 additions & 1 deletion src/Menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export default function makeMenu(spec: Spec) {
const searchContainer = spec.doc.createElement('div');
searchContainer.setAttribute('id', 'menu-search');
searchContainer.innerHTML =
'<input type=text id=menu-search-box placeholder=Search...><div id=menu-search-results class=inactive></div>';
'<div id=menu-search-row><input type=text id=menu-search-box placeholder=Search...>' +
'<button type=button id=color-scheme-toggle title="Toggle light/dark mode (d)" aria-label="Toggle light/dark mode">' +
'<svg viewBox="0 0 16 16" width=16 height=16 aria-hidden=true><circle cx=8 cy=8 r=6.5 fill=none stroke=currentColor stroke-width=1.5 /><path d="M8 1.5a6.5 6.5 0 0 1 0 13z" fill=currentColor /></svg>' +
'</button></div><div id=menu-search-results class=inactive></div>';

const menuContainer = spec.doc.createElement('div');
menuContainer.setAttribute('id', 'menu');
Expand Down
1 change: 1 addition & 0 deletions src/Spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,7 @@ ${await utils.readFile(path.join(__dirname, '../js/multipage.js'))}
<li><span>Toggle shortcuts help</span><code>?</code></li>
<li><span>Toggle "can call user code" annotations</span><code>u</code></li>
<li><span>Toggle early exit annotations</span><code>e</code></li>
<li><span>Toggle light/dark mode</span><code>d</code></li>
${this.opts.multipage ? `<li><span>Navigate to/from multipage</span><code>m</code></li>` : ''}
<li><span>Jump to search box</span><code>/</code></li>
<li><span>Toggle pinning of the current clause</span><code>p</code></li>
Expand Down
1 change: 1 addition & 0 deletions test/baselines/generated-reference/abstract-methods.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/algorithms.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 70 additions & 3 deletions test/baselines/generated-reference/assets-inline.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/autolinking.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/boilerplate-all.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/charset-absent.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/charset-present.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/code.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/copyright.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/date.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/dfn.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/duplicate-ids.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/ecmarkdown.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/effect-user-code.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/emd-in-grammar.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/baselines/generated-reference/eqn.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading