# Dark Mode Support Design Date: 2026-05-01 Project: Mainty Status: Draft approved by user for implementation planning ## Goal Add dark mode support across all regular app views with a floating dark/light toggle in the top-right corner at all times (for normal JS-enabled operation). Theme should default to the system preference when no user preference exists, and user preference should persist in a cookie. ## In Scope - Add theme resolution and apply logic for: - `views/index.php` - `views/vehicle.php` - `views/settings.php` - `views/login.php` - `views/setup.php` - Add a floating top-right theme toggle visible on all in-scope views. - Persist user-selected theme in a cookie. - Apply cookie preference on view load. ## Out of Scope - No database persistence for theme preference. - No account-level sync across devices. - No changes to route/controller/model logic. - No dark mode support for export/print page. ## Explicit Product Decisions - Export page remains always light for print readability: - `views/export.php` must ignore cookie and system theme. - If JavaScript is disabled, app falls back to light theme. ## Architecture ## 1) Shared Theme Partial Create a reusable partial: `views/partials/theme.php`. Responsibilities: - Inject minimal theme bootstrap script in `` to resolve theme and apply `dark` class on `` as early as possible. - Inject floating theme toggle markup near the start/end of ``. - Inject shared theme CSS variables and a concise set of utility mappings for common surfaces and text colors. - Expose small JS helpers for: - reading cookie, - writing cookie, - resolving effective theme, - updating UI state/icon/labels, - toggling theme on click. Why: - Keeps behavior consistent across all views. - Avoids duplicating script/CSS/button logic in each template. - Reduces maintenance cost for future theme adjustments. Interface contract: - The partial is included with an explicit section selector: - `$themeSection = 'head'; include __DIR__ . '/partials/theme.php';` - `$themeSection = 'body'; include __DIR__ . '/partials/theme.php';` - `'head'` section outputs only bootstrap script + shared theme styles. - `'body'` section outputs only floating toggle markup + behavior wiring script. - Any other/missing section outputs nothing (safe no-op). ## 2) View Integration Points Integrate the partial into each in-scope view. - Include contract per themed view: - In `` (after Tailwind CDN script/link and before ``): set `$themeSection = 'head'` and include `views/partials/theme.php`. - In `` (before page-local scripts and before ``): set `$themeSection = 'body'` and include `views/partials/theme.php`. - Partial integration must not require any page-specific IDs. - Partial must be self-contained and safe to include on all in-scope views without per-view conditionals. Each view keeps existing layout and structure; only color semantics are adjusted where required for dark compatibility. ## 3) Export Exception `views/export.php` remains standalone light styling: - No theme partial include. - No floating toggle. - No dark class application. ## Data Flow ## Initial Load 1. Parse cookie `theme`. 2. If cookie is `dark` or `light`, use it. 3. Else query `window.matchMedia('(prefers-color-scheme: dark)')`. 4. Apply result by toggling `document.documentElement.classList` with `dark`. 5. Render page using applied theme classes/variables. ## User Toggle Interaction 1. User clicks floating toggle. 2. Determine next theme (`light` -> `dark`, `dark` -> `light`). 3. Update `dark` class on `` immediately. 4. Attempt to write cookie `theme=` with long-lived expiration, `path=/`, `SameSite=Lax`. 5. Update toggle icon/accessible label/title to reflect new mode. 6. If cookie persistence fails (blocked by browser/privacy settings), keep the switched theme for the current document and do not show an error; next navigation/load falls back to normal resolution flow (system preference if no valid cookie). ## Subsequent Loads - Cookie value takes precedence over system preference. - Users with no valid cookie continue following system theme. ## Theme Styling Strategy Use a hybrid of CSS variables + targeted Tailwind class updates. - Keep existing spacing/layout/structure classes. - Introduce semantic surface/text variable mappings for repeated patterns. - Update key hardcoded light-only color usages in templates where needed: - page backgrounds, - headers/cards/panels, - tables (head/body row hover), - forms and inputs, - modals/dropdowns, - flash messages. Acceptance scope boundary for theming edits: - Only update color-related classes/styles needed for readability/contrast in dark mode. - Do not change spacing, typography scale, layout structure, or route/form behavior. - A view is considered complete when all listed surface types in that view remain readable and interactive in both light and dark modes. Action colors (primary blue, danger red) remain recognizable but are adjusted for contrast in dark contexts. ## Component Requirements ## Floating Toggle Button - Position: fixed top-right, always visible in viewport. - Layering: `z-index: 50`, intended to remain visible above regular page content and app modals. - Visual states: - sun icon when dark mode is active (indicates switch to light), - moon icon when light mode is active (indicates switch to dark). - Accessibility: - semantic `