- Move cross-cutting docs (personas, design system, implementation phases, Ideen.md) to .specify/memory/ - Move cross-cutting research and plans to .specify/memory/research/ and .specify/memory/plans/ - Extract 5 setup tasks from spec/setup-tasks.md into individual specs/001-005/spec.md files with spec-kit template format - Extract 20 user stories from spec/userstories.md into individual specs/006-026/spec.md files with spec-kit template format - Relocate feature-specific research and plan docs into specs/[feature]/ - Add spec-kit constitution, templates, scripts, and slash commands - Slim down CLAUDE.md to Claude-Code-specific config, delegate principles to .specify/memory/constitution.md - Update ralph.sh with stream-json output and per-iteration logging - Delete old spec/ and docs/agents/ directories - Gitignore Ralph iteration JSONL logs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.2 KiB
Feature Specification: Dark/Light Mode
Feature: 023-dark-mode
Created: 2026-03-06
Status: Draft
Source: Migrated from spec/userstories.md
User Scenarios & Testing
User Story 1 - System preference respected on first visit (Priority: P1)
A user opens the app for the first time. The app automatically adopts their operating system or browser dark/light preference without any manual configuration required. No preference data is transmitted to the server.
Why this priority: This is the baseline behavior — it works without any user interaction and provides the correct experience immediately.
Independent Test: Can be tested by opening the app in a browser with prefers-color-scheme: dark set at the OS level and verifying the dark theme is applied, then repeating with light preference.
Acceptance Scenarios:
- Given a user opens the app for the first time with no manual preference stored, When the OS/browser preference is
prefers-color-scheme: dark, Then the app renders in dark mode. - Given a user opens the app for the first time with no manual preference stored, When the OS/browser preference is
prefers-color-scheme: light, Then the app renders in light mode. - Given the app is rendering in either mode, When the page is loaded, Then no server request is made and no preference data is transmitted.
User Story 2 - Manual toggle overrides system preference (Priority: P1)
A user can switch between dark and light mode using a visible toggle available on any page. Their choice is persisted in localStorage and takes precedence over the OS preference on all subsequent visits.
Why this priority: The explicit user preference must be honoured and must persist — without this, the toggle would reset on every visit, making it unusable.
Independent Test: Can be tested by toggling the mode, closing and reopening the browser, and verifying the manually selected mode is still active even if it differs from the OS preference.
Acceptance Scenarios:
- Given the app is in light mode (system preference), When the user activates the dark mode toggle, Then the UI immediately switches to dark mode and the preference is stored in localStorage.
- Given the user has a dark mode preference stored in localStorage, When the user revisits the app, Then dark mode is applied regardless of the current OS/browser preference.
- Given the user has a light mode preference stored in localStorage and the OS preference is dark, When the user revisits the app, Then light mode is applied (localStorage takes precedence).
- Given the app is running, When the user toggles the mode, Then no server request is made and no preference data is transmitted.
User Story 3 - Toggle accessible from any page (Priority: P2)
The dark/light mode toggle is reachable from every page of the app — event pages, local event overview, creation form, etc. — so the user never has to navigate away to change their preference.
Why this priority: This is a usability enhancement. The feature works without it (if the toggle were only on one page), but accessibility from any page is important for a good experience.
Independent Test: Can be tested by navigating to the event page, the local event overview, and the creation form and verifying the toggle is visible and functional on each.
Acceptance Scenarios:
- Given the user is on the local event overview page (
/), When they look for the mode toggle, Then it is visible and functional. - Given the user is on an event page, When they look for the mode toggle, Then it is visible and functional.
- Given the user is on the event creation form, When they look for the mode toggle, Then it is visible and functional.
User Story 4 - Dark/light mode does not affect event-level color themes (Priority: P2)
Dark/light mode affects only the app's global UI chrome (navigation, local event overview, forms, etc.). Individual event pages use their own color theme (US-15), which is independent of the app-level dark/light setting.
Why this priority: Necessary to define the boundary between app-level theming and event-level theming clearly, but secondary to the core toggle behaviour.
Independent Test: Can be tested by creating an event with a custom color theme, then toggling dark/light mode and verifying the event page theme is unaffected while the surrounding chrome does change.
Acceptance Scenarios:
- Given an event page is rendered with a custom color theme (US-15), When the user switches the app to dark mode, Then the event page color theme remains unchanged (only surrounding chrome changes).
- Given the app is in dark mode, When the user navigates to the local event overview, Then the overview uses the dark color scheme.
User Story 5 - Both modes meet WCAG AA contrast (Priority: P1)
Both dark and light modes must meet accessibility contrast requirements at the WCAG AA minimum level, ensuring the app is usable for users with visual impairments in both modes.
Why this priority: Accessibility is a baseline requirement per the project statutes, not an afterthought.
Independent Test: Can be tested using automated contrast checking tools against both mode variants.
Acceptance Scenarios:
- Given the app is in dark mode, When text and interactive elements are checked for contrast ratio, Then all text/background pairings meet WCAG AA minimum (4.5:1 for normal text, 3:1 for large text).
- Given the app is in light mode, When text and interactive elements are checked for contrast ratio, Then all text/background pairings meet WCAG AA minimum.
Edge Cases
- What happens when the OS
prefers-color-schemevalue changes while the app is open (e.g. user switches OS theme at runtime)? If no manual preference is stored in localStorage, should the app react? [NEEDS EXPANSION during implementation] - What happens when localStorage is unavailable (private browsing with strict settings)? The system preference fallback must still work without crashing.
- How does app-level dark/light mode interact with the event-level color themes (US-15) when an event page is embedded in the app chrome? Themes should remain readable in both modes.
Requirements
Functional Requirements
- FR-001: The app MUST detect and apply
prefers-color-schemeas the default on first visit when no manual preference is stored in localStorage. - FR-002: The app MUST provide a visible toggle UI element to switch between dark and light mode, accessible from any page.
- FR-003: The app MUST persist the user's manual mode preference in localStorage and apply it on subsequent visits, overriding the system preference.
- FR-004: Dark/light mode MUST affect all global app chrome: navigation, local event overview, event creation/editing forms, and all non-event-page UI elements.
- FR-005: Dark/light mode MUST NOT affect individual event page color themes (US-15); event pages are styled independently.
- FR-006: The mode switch MUST be entirely client-side; no server request is made and no preference data is transmitted.
- FR-007: Both dark and light modes MUST meet WCAG AA contrast requirements for all text and interactive elements.
- FR-008: The toggle MUST be accessible (keyboard-navigable, labelled for screen readers).
Key Entities
- DarkLightPreference: A client-side-only value (
"dark"|"light"| absent) stored in localStorage. No server-side equivalent. Determines which CSS theme is applied to the global app chrome.
Success Criteria
Measurable Outcomes
- SC-001: On first visit with
prefers-color-scheme: dark, the dark theme is applied without any user interaction. - SC-002: A user's manual toggle selection persists across browser sessions and overrides the OS preference.
- SC-003: The mode toggle is visible and functional on all primary app pages (local event overview, event page, creation form).
- SC-004: Automated contrast checks pass WCAG AA thresholds for all text elements in both dark and light modes.
- SC-005: No network request is made when toggling the mode or when the stored preference is applied on page load.