Files
mainty/docs/superpowers/specs/2026-05-01-dark-mode-design.md
T

8.2 KiB

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 <head> to resolve theme and apply dark class on <html> as early as possible.
  • Inject floating theme toggle markup near the start/end of <body>.
  • 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 <head> (after Tailwind CDN script/link and before </head>): set $themeSection = 'head' and include views/partials/theme.php.
    • In <body> (before page-local scripts and before </body>): 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 <html> immediately.
  4. Attempt to write cookie theme=<dark|light> 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 <button type="button">,
    • aria-label updates per state,
    • visible focus ring,
    • keyboard operable by default.
  • Name: theme
  • Allowed values: dark, light
  • Ignore any other value as invalid.
  • Attributes:
    • path=/
    • SameSite=Lax
    • expiration max-age=31536000 (1 year)

Error Handling and Edge Cases

  • Invalid cookie value -> treat as unset, fallback to system preference.
  • matchMedia unavailable (unlikely) -> fallback light.
  • JS disabled -> light theme only, app remains functional, and floating toggle is not shown.
  • Cookie write blocked/fails -> keep current-page visual switch only; persistence not guaranteed on next load.
  • System theme changes after load:
    • if cookie exists, keep explicit user preference,
    • if cookie absent, follow resolved system preference on next navigation/load.

Testing and Verification

Because no automated test suite is configured, verification is manual + syntax checks.

Manual Functional Checks

  • Open each in-scope view and verify toggle is visible at top-right:
    • /home, vehicle details, /settings, /login, /setup.
  • With no cookie set:
    • verify first load follows system preference.
  • Click toggle:
    • verify immediate theme switch,
    • verify cookie is written,
    • verify choice persists across reload and cross-page navigation.
  • Set an invalid cookie value manually:
    • verify fallback to system preference.
  • Open export page:
    • verify always light and no toggle.
  • Disable JS (or simulate no JS):
    • verify app remains usable in light mode and toggle is not rendered.
  • Simulate system-theme scenario with no cookie:
    • verify resolved theme follows current system preference on load.

Definition of Done

  • views/partials/theme.php exists and is included in all in-scope views only.
  • Floating top-right toggle appears on all in-scope views and is keyboard accessible.
  • Theme resolution order is enforced: valid cookie first, otherwise system preference, otherwise light fallback.
  • Clicking toggle updates theme immediately and attempts cookie persistence.
  • Invalid cookie values are ignored safely.
  • Export page remains always light and has no toggle.
  • php -l passes for each changed PHP file.

Syntax Checks

Run php -l for each changed PHP file:

  • views/partials/theme.php
  • each modified view file in scope

Risks and Mitigations

  • Risk: visual regressions from many color classes.
    • Mitigation: limit edits to color semantics; keep structure/layout intact.
  • Risk: flash of incorrect theme.
    • Mitigation: apply theme class in early <head> script before body render.
  • Risk: inconsistent behavior across pages.
    • Mitigation: centralize logic in shared partial.

Implementation Boundaries

  • No refactors unrelated to theme support.
  • Keep MVC boundaries untouched (view-only changes).
  • Keep existing UX flows, redirects, and auth/setup guards unchanged.