Skip to content

Add dark/light theme toggle with system preference detection#704

Draft
Copilot wants to merge 17 commits intomainfrom
copilot/add-dark-light-theme-toggle
Draft

Add dark/light theme toggle with system preference detection#704
Copilot wants to merge 17 commits intomainfrom
copilot/add-dark-light-theme-toggle

Conversation

Copy link
Contributor

Copilot AI commented Mar 1, 2026

  • Create js/theme-toggle.js
  • Update styles.css and dashboard/styles.css
  • Update src/browser/shared/theme.ts
  • Update all 14 main + 14 dashboard HTML language variants
  • Fix JSDoc, comments, scan-line, !important (round 1)
  • Fix anti-flash snippet, addListener compat, border-bottom: revert, mobile touch target (round 2)
  • Remove redundant header { position: relative; }, add theme to dashboard pages, fix getActiveThemeColors() fallback, fix tooltip colors (round 3)
  • Remove redundant keydown handler for Enter/Space from .theme-toggle-btn (round 4)
  • Localize toggle button aria-label/title/data-label-* in all 13 non-English dashboard pages
  • Fix updateButton timing and getActiveThemeColors() JSDoc, remove dead imports (round 5)
  • Fix ESLint no-empty CI failure — added /* storage unavailable */ to empty catch blocks in theme-toggle.js
  • Validate localStorage value in system theme-change handler — only 'dark'/'light' count as explicit choices; invalid values are cleared
  • Add tickColor/gridColor fields to ThemeColors interface and both theme palettes
  • Derive legend label, axis tick, and grid colors from active theme in chart-factory.ts; cache single getActiveThemeColors() call per getResponsiveOptions() invocation
  • Build passes (✓ lint clean, TypeScript clean)
  • All 2286 tests pass
Original prompt

This section details on the original issue you should resolve

<issue_title>Add dark/light theme toggle with system preference detection</issue_title>
<issue_description>## 📋 Issue Type
UI/UX / Feature

🎯 Objective

Add a dark/light theme toggle to improve readability and user comfort, while maintaining the cyberpunk aesthetic as the default theme.

📊 Current State

  • The site uses a fixed dark cyberpunk theme (dark-bg: #0a0e27)
  • No way for users to switch to a lighter theme
  • Some users may have difficulty reading light text on dark backgrounds
  • prefers-color-scheme media query is not utilized
  • Dashboard charts and visualizations are hardcoded to dark theme colors

🚀 Desired State

  • CSS-only theme toggle (dark/light) using CSS custom properties
  • Respect prefers-color-scheme system preference
  • User preference persisted in localStorage
  • Dashboard charts adapt colors to active theme
  • Toggle accessible via keyboard and screen readers
  • Theme consistent across all 14 language variants

🔧 Implementation Approach

  1. Define light theme CSS custom properties:
[data-theme="light"] {
  --dark-bg: #f5f5f5;
  --mid-bg: #e0e0e0;
  --light-text: #1a1e3d;
  --primary-cyan: #0077b6;
  /* ... etc */
}
  1. Add theme toggle button to header navigation
  2. JavaScript to toggle data-theme attribute on <html> element
  3. Save preference to localStorage
  4. Detect prefers-color-scheme for initial theme
  5. Update Chart.js theme colors in src/browser/shared/theme.ts
  6. Test across all page types and language variants

🤖 Recommended Agent

ui-enhancement-specialist — Theme system implementation

✅ Acceptance Criteria

  • Light and dark themes available via toggle button
  • System preference (prefers-color-scheme) respected
  • User preference persisted in localStorage
  • Toggle accessible (keyboard, screen reader)
  • Charts and visualizations adapt to theme
  • Color contrast ≥ 4.5:1 in both themes
  • All 14 language variants work with both themes
  • RTL layouts correct in both themes

📚 References

  • Theme: src/browser/shared/theme.ts
  • Styles: styles.css
  • Dashboard styles: dashboard/styles.css
  • WCAG: Color contrast requirements

🏷️ Labels

type:feature, priority:low, component:ui-ux</issue_description>

<agent_instructions>Add dark/light theme toggle using CSS custom properties. Define light theme in styles.css. Add toggle button to header navigation. Save preference in localStorage. Respect prefers-color-scheme. Ensure 4.5:1 contrast in both themes.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

- Create js/theme-toggle.js with localStorage persistence,
  prefers-color-scheme detection, keyboard accessibility, and
  window.riksdagsToggleTheme / riksdagsGetTheme exports

- Add [data-theme='dark'] and [data-theme='light'] CSS blocks to
  styles.css, covering all CSS custom properties plus element-level
  overrides (scan lines, Orbitron font, glows). Includes cyberpunk
  design tokens (--dark-bg, --mid-bg, --light-text, --primary-cyan)
  matching spec requirements. WCAG 2.1 AA contrast in both themes.

- Add .theme-toggle-btn CSS with 44px touch target, focus ring,
  and RTL positioning for Arabic/Hebrew variants.

- Update dashboard/styles.css with [data-theme] overrides for chart
  sections, metric cards, and chart containers.

- Update all 14 HTML language files (index.html, index_sv.html,
  index_da.html, index_no.html, index_fi.html, index_de.html,
  index_fr.html, index_es.html, index_nl.html, index_ar.html,
  index_he.html, index_ja.html, index_ko.html, index_zh.html):
  * Anti-flash inline script in <head> to prevent flash-of-wrong-theme
  * Theme toggle button in <header> with translated aria-labels
  * <script src='/js/theme-toggle.js'> before module entry point

- Rewrite src/browser/shared/theme.ts with dual-theme support:
  * DARK_THEME_COLORS / LIGHT_THEME_COLORS constants
  * getActiveThemeColors() runtime palette resolver
  * CHART_PALETTE_LIGHT and getChartPalette() for Chart.js
  * onThemeChange(callback) MutationObserver subscription
  * THEME_COLORS kept as deprecated alias (v2.0, removal in v3.0)
  * getPartyColor() uses theme-aware fallback cyan

Co-authored-by: Copilot <copilot@github.com>
Copilot AI changed the title [WIP] Add dark/light theme toggle with system preference detection Add dark/light theme toggle with system preference detection Mar 1, 2026
@github-actions github-actions bot added dashboard Interactive dashboards (Chart.js/D3.js) html-css HTML/CSS changes javascript JavaScript code changes i18n Internationalization/localization translation Translation updates rtl RTL language support (Arabic, Hebrew) refactor Code refactoring size-l Large change (250-1000 lines) labels Mar 1, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a dark/light theme system to the static site with an accessible toggle, OS preference detection, and the start of a runtime theme API intended for Chart.js/dashboard consumers.

Changes:

  • Introduces html[data-theme="dark"|"light"] CSS variable overrides plus theme-toggle button styling.
  • Adds a new js/theme-toggle.js script (localStorage + prefers-color-scheme) and injects anti-flash + toggle markup into all 14 language index pages.
  • Extends src/browser/shared/theme.ts with dark/light palettes and runtime helpers (getActiveThemeColors, getChartPalette, onThemeChange).

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
styles.css Adds [data-theme] variable overrides and .theme-toggle-btn styling to support dark/light themes.
src/browser/shared/theme.ts Adds dual theme palettes and runtime helpers intended for theme-aware Chart.js configuration.
js/theme-toggle.js Implements theme resolution/toggling with persistence and system preference following.
dashboard/styles.css Adds theme-specific dashboard container styling for better dark/light presentation.
index.html Adds anti-flash theme init, header toggle button, and loads js/theme-toggle.js.
index_sv.html Adds anti-flash theme init, header toggle button (SV), and loads js/theme-toggle.js.
index_da.html Adds anti-flash theme init, header toggle button (DA), and loads js/theme-toggle.js.
index_no.html Adds anti-flash theme init, header toggle button (NO), and loads js/theme-toggle.js.
index_fi.html Adds anti-flash theme init, header toggle button (FI), and loads js/theme-toggle.js.
index_de.html Adds anti-flash theme init, header toggle button (DE), and loads js/theme-toggle.js.
index_fr.html Adds anti-flash theme init, header toggle button (FR), and loads js/theme-toggle.js.
index_es.html Adds anti-flash theme init, header toggle button (ES), and loads js/theme-toggle.js.
index_nl.html Adds anti-flash theme init, header toggle button (NL), and loads js/theme-toggle.js.
index_ar.html Adds anti-flash theme init, header toggle button (AR), and loads js/theme-toggle.js.
index_he.html Adds anti-flash theme init, header toggle button (HE), and loads js/theme-toggle.js.
index_ja.html Adds anti-flash theme init, header toggle button (JA), and loads js/theme-toggle.js.
index_ko.html Adds anti-flash theme init, header toggle button (KO), and loads js/theme-toggle.js.
index_zh.html Adds anti-flash theme init, header toggle button (ZH), and loads js/theme-toggle.js.

styles.css Outdated
Comment on lines 10431 to 10445
/* Scan-line effect for dark theme */
html[data-theme="dark"] body::after {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: repeating-linear-gradient(
transparent 0px,
rgba(0, 0, 0, 0.05) 1px,
transparent 2px
);
pointer-events: none;
z-index: 9999;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

html[data-theme="dark"] body::after duplicates the existing scan-line effect defined earlier under @media (prefers-color-scheme: dark) { body::after ... }. Duplicating the same styling in two places increases the risk they drift; consider centralizing the shared definition and only toggling it via [data-theme].

Suggested change
/* Scan-line effect for dark theme */
html[data-theme="dark"] body::after {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: repeating-linear-gradient(
transparent 0px,
rgba(0, 0, 0, 0.05) 1px,
transparent 2px
);
pointer-events: none;
z-index: 9999;
/* Adjust scan-line effect opacity for dark theme
Base scan-line effect is defined in the prefers-color-scheme media query. */
html[data-theme="dark"] body::after {

Copilot uses AI. Check for mistakes.
* Theme architecture:
* – `DARK_THEME_COLORS` → Ingress-inspired neon palette (matches html[data-theme="dark"])
* – `LIGHT_THEME_COLORS` → Professional green palette (matches html[data-theme="light"] / :root)
* – `THEME_COLORS` → Re-exported alias for the active theme (runtime-resolved)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The module-level "Theme architecture" doc says THEME_COLORS is a runtime-resolved alias for the active theme, but in the implementation it is a constant alias to DARK_THEME_COLORS (and marked deprecated later). Please update the header comment to match the actual behavior to avoid misleading consumers.

Suggested change
* `THEME_COLORS` Re-exported alias for the active theme (runtime-resolved)
* `THEME_COLORS` Deprecated constant alias of `DARK_THEME_COLORS` (kept for backwards compatibility; not runtime-resolved)

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +118
/**
* Cyberpunk (dark) theme color palette — kept as the default export for
* backwards compatibility with existing Chart.js consumers.
*
* @deprecated since v2.0 — will be removed in v3.0.
* Prefer `getActiveThemeColors()` which returns the correct palette for the
* current `data-theme` value. Migration:
* ```ts
* // Before
* import { THEME_COLORS } from './theme.js';
* chart.data.datasets[0].borderColor = THEME_COLORS.cyan;
*
* // After
* import { getActiveThemeColors } from './theme.js';
* chart.data.datasets[0].borderColor = getActiveThemeColors().cyan;
* ```
*/
export const THEME_COLORS: ThemeColors = DARK_THEME_COLORS;

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

THEME_COLORS is still exported as a constant alias to DARK_THEME_COLORS. In the current codebase, multiple dashboards import THEME_COLORS via ../shared/index.js, so switching to light theme will not update chart colors (and can lead to low contrast on light backgrounds). To make charts truly theme-aware, either migrate those consumers to call getActiveThemeColors() (and/or subscribe via onThemeChange()), or provide an exported value that resolves to the active palette at runtime.

Copilot uses AI. Check for mistakes.
* already made an explicit choice.
*
* Accessibility:
* - Toggle button carries `role="button"`, `aria-pressed`, and a descriptive
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The JSDoc claims the toggle button carries role="button", but the implementation relies on a native <button> element (which already has the correct implicit role) and does not set a role attribute. Consider removing/adjusting that statement to keep the accessibility docs accurate.

Suggested change
* - Toggle button carries `role="button"`, `aria-pressed`, and a descriptive
* - Toggle is a native `<button>` with `aria-pressed` and a descriptive

Copilot uses AI. Check for mistakes.

// Apply before first paint (called synchronously by the anti-flash snippet
// already present in <head>; this line covers when the module loads later).
applyTheme(resolveTheme(), false /* already persisted by anti-flash */);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This boot comment says the theme was "already persisted by anti-flash", but the inline anti-flash snippet (and this call with persist=false) only reads localStorage and sets data-theme—it doesn't write anything. Please update the comment (or adjust the logic if you intended initial resolution to be persisted) so the behavior is clear.

Suggested change
applyTheme(resolveTheme(), false /* already persisted by anti-flash */);
applyTheme(resolveTheme(), false /* initial resolution only; do not persist on module boot */);

Copilot uses AI. Check for mistakes.
Comment on lines 133 to +140
/**
* Chart color palette for sequential data series.
* Chart color palette for sequential data series (dark theme).
*/
export const CHART_PALETTE = [
THEME_COLORS.cyan,
THEME_COLORS.magenta,
THEME_COLORS.yellow,
THEME_COLORS.green,
THEME_COLORS.orange,
THEME_COLORS.purple,
THEME_COLORS.red,
THEME_COLORS.blue,
DARK_THEME_COLORS.cyan,
DARK_THEME_COLORS.magenta,
DARK_THEME_COLORS.yellow,
DARK_THEME_COLORS.green,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

CHART_PALETTE remains hardwired to the dark palette. Since dashboards import CHART_PALETTE today, charts won’t automatically adapt when data-theme changes. Consider switching call sites to getChartPalette() (and updating on theme changes) or otherwise ensuring the exported palette used by charts reflects the active theme.

Copilot uses AI. Check for mistakes.
styles.css Outdated
/* Disable dark element-level overrides when user forces light */
html[data-theme="light"] body::after,
html[data-theme="light"] body::before {
display: none !important;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

html[data-theme="light"] body::after/body::before uses display: none !important;, but the selector already has higher specificity than the earlier @media (prefers-color-scheme: dark) { body::after/body::before ... } rules. The !important is unnecessary here and makes future overrides harder; consider dropping it.

Suggested change
display: none !important;
display: none;

Copilot uses AI. Check for mistakes.
@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

Applied all 7 review comments in commit 204d0df:

  • JSDoc accuracy (js/theme-toggle.js): Corrected "carries role="button"" → "native <button>"; updated misleading "already persisted by anti-flash" comment to "initial resolution only; do not persist on module boot"
  • theme.ts doc: THEME_COLORS is now described as "Deprecated constant alias of DARK_THEME_COLORS (kept for backwards compatibility; not runtime-resolved)"
  • chart-factory.ts (core theming fix): Tooltip titleColor/borderColor now call getActiveThemeColors() at render time instead of the static THEME_COLORS constant, so chart tooltips reflect the active theme. Also re-exports getActiveThemeColors and getChartPalette for dashboard consumers.
  • styles.css: Removed !important from html[data-theme="light"] body::after/before { display: none } (specificity is already sufficient); updated scan-line comment to explain the media-query overlap.

@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

Fixed in commit c12796b: removed the redundant keydown handler for Enter/Space from js/theme-toggle.js. Native <button> elements already synthesize a click event on both keys, so the explicit handler was unnecessary.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 16 comments.

Comment on lines 111 to 114
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings (as done on the main index_* pages) so screen readers and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="Byt till mörkt tema"
title="Byt till mörkt tema"
data-label-dark="Byt till ljust tema"
data-label-light="Byt till mörkt tema">

Copilot uses AI. Check for mistakes.
Comment on lines 111 to 114
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="Passer au thème sombre"
title="Passer au thème sombre"
data-label-dark="Passer au thème clair"
data-label-light="Passer au thème sombre">

Copilot uses AI. Check for mistakes.
Comment on lines 111 to 114
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="Schakel over naar donkere modus"
title="Schakel over naar donkere modus"
data-label-dark="Schakel over naar lichte modus"
data-label-light="Schakel over naar donkere modus">

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 115
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="切换到深色主题"
title="切换到深色主题"
data-label-dark="切换到浅色主题"
data-label-light="切换到深色主题">

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 115
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="다크 테마로 전환"
title="다크 테마로 전환"
data-label-dark="라이트 테마로 전환"
data-label-light="다크 테마로 전환">

Copilot uses AI. Check for mistakes.
Comment on lines 111 to 114
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="Zum dunklen Design wechseln"
title="Zum dunklen Design wechseln"
data-label-dark="Zum hellen Design wechseln"
data-label-light="Zum dunklen Design wechseln">

Copilot uses AI. Check for mistakes.
Comment on lines 111 to 114
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="Cambiar a tema oscuro"
title="Cambiar a tema oscuro"
data-label-dark="Cambiar a tema claro"
data-label-light="Cambiar a tema oscuro">

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 115
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="ダークテーマに切り替え"
title="ダークテーマに切り替え"
data-label-dark="ライトテーマに切り替え"
data-label-light="ダークテーマに切り替え">

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 115
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This dashboard page is localized, but the theme toggle’s aria-label/title/data-label-* strings are still English. Please localize these strings so assistive tech and tooltips match the page language.

Suggested change
aria-label="Switch to dark theme"
title="Switch to dark theme"
data-label-dark="Switch to light theme"
data-label-light="Switch to dark theme">
aria-label="التبديل إلى الوضع الداكن"
title="التبديل إلى الوضع الداكن"
data-label-dark="التبديل إلى الوضع الفاتح"
data-label-light="التبديل إلى الوضع الداكن">

Copilot uses AI. Check for mistakes.
Comment on lines 88 to 92
* Returns the active ThemeColors based on the current `data-theme` attribute
* on `<html>`. When the attribute is absent (e.g. before the anti-flash
* snippet runs), falls back to `prefers-color-scheme` — consistent with the
* CSS `@media (prefers-color-scheme: dark)` default. Only hard-defaults to
* dark when neither attribute nor `matchMedia` is available (SSR/test).
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The JSDoc for getActiveThemeColors() says it “hard-defaults to dark when neither attribute nor matchMedia is available”, but the implementation returns LIGHT_THEME_COLORS when document exists and window.matchMedia is unavailable. Update the comment (or adjust the fallback) so it matches the actual behavior and the CSS default you intend to mirror.

Copilot uses AI. Check for mistakes.
@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…SDoc, remove dead imports

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

Applied all 16 review comments in commit 70eb81c:

  • Localized toggle button strings in all 13 non-English dashboard pages (sv, fr, nl, no, fi, da, de, es, ar, he, zh, ko, ja) — aria-label/title/data-label-* now match each page language
  • updateButton timing (js/theme-toggle.js): now called immediately after applyTheme() so button icon/label is correct from first render, not only after DOMContentLoaded
  • getActiveThemeColors() JSDoc (theme.ts): corrected to accurately state the browser fallback is LIGHT_THEME_COLORS when matchMedia is unavailable; DARK_THEME_COLORS only returned in SSR/test
  • Dead import cleanup (party-, coalition-, ministry-, risk-dashboard): removed unused THEME_COLORS and CHART_PALETTE imports — chart colors are already driven by getActiveThemeColors() inside chart-factory.ts

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 39 out of 39 changed files in this pull request and generated 2 comments.

Comment on lines 58 to 62
backgroundColor: getActiveThemeColors().tooltipBg,
titleColor: getActiveThemeColors().cyan,
bodyColor: getActiveThemeColors().bodyText,
borderColor: getActiveThemeColors().cyan,
borderWidth: 1,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

getResponsiveOptions() updates tooltip colors based on getActiveThemeColors(), but other chart UI colors remain hardcoded for the dark theme (e.g. legend label color, axis tick colors, grid colors). In light theme this will likely reduce contrast/readability and make charts look inconsistent. Consider deriving these remaining colors from the active theme (and cache the resolved ThemeColors once per call to avoid repeated lookups).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 9c7e0a2. Added tickColor and gridColor fields to the ThemeColors interface and both palettes (dark: #a0a0a0 / rgba(255,255,255,0.07), light: #555555 / rgba(0,0,0,0.08)). getResponsiveOptions() now calls getActiveThemeColors() once (cached in theme), and derives legend label, axis tick, and grid colors from the active theme — no more hardcoded dark-only values.

var mql = window.matchMedia('(prefers-color-scheme: dark)');
var handleSchemeChange = function (e) {
try {
if (localStorage.getItem(STORAGE_KEY)) return; // explicit choice wins
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

In the system theme-change handler, if (localStorage.getItem(STORAGE_KEY)) return; treats any stored value (including an unexpected/legacy one) as an explicit user preference, which can prevent the page from following system theme changes. Consider validating the stored value (only 'dark'/'light' count as explicit) or clearing invalid values before deciding to ignore system changes.

Suggested change
if (localStorage.getItem(STORAGE_KEY)) return; // explicit choice wins
var storedTheme = localStorage.getItem(STORAGE_KEY);
if (storedTheme === DARK || storedTheme === LIGHT) {
return; // explicit valid choice wins
}
// Clear invalid or legacy values so system preference can apply
if (storedTheme !== null) {
localStorage.removeItem(STORAGE_KEY);
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 9c7e0a2. The handler now reads the stored value, only returns early if it equals DARK or LIGHT, and calls localStorage.removeItem for any other non-null value so invalid/legacy entries don't block system preference changes.

@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread and Quality Checks / typescript-lint (pull_request)
Quality Checks / typescript-lint (pull_request)Failing after 43s

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…s from active theme

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot apply changes based on the comments in this thread and [Quality Checks / typescript-lint (pull_request)](htt...

Applied both review comments and fixed the TypeScript lint CI failure in commit 9c7e0a2:

  • chart-factory.ts: All chart UI colors (legend labels, axis ticks, grid lines) now derive from the active theme via a single cached getActiveThemeColors() call — added tickColor/gridColor fields to ThemeColors and both palettes.
  • theme-toggle.js: localStorage value is now validated (only 'dark'/'light' counts as an explicit preference; invalid values are cleared). Empty catch blocks now have /* storage unavailable */ comments to satisfy ESLint no-empty.

ESLint is clean, TypeScript compiles without errors, all 2286 tests pass.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard Interactive dashboards (Chart.js/D3.js) html-css HTML/CSS changes i18n Internationalization/localization javascript JavaScript code changes refactor Code refactoring rtl RTL language support (Arabic, Hebrew) size-l Large change (250-1000 lines) size-xl Extra large change (> 1000 lines) translation Translation updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add dark/light theme toggle with system preference detection

3 participants