Files
mainty/docs/superpowers/specs/2026-05-17-theme-toggle-topbar-design.md

7.4 KiB

Theme Toggle Top-Bar Relocation Design

Context

The application currently renders the light/dark toggle as a floating fixed button from views/partials/theme.php. The requested behavior is to place this control in the top bar, positioned to the left of the settings button when settings is present, while keeping the toggle visible and usable across all views (including login and setup).

Goals

  • Replace the floating theme toggle with a top-bar-integrated control.
  • Keep theme behavior unchanged (cookie persistence, icon/label updates, immediate visual switch).
  • Ensure the toggle is visible and reachable in every application view.
  • Preserve existing visual patterns (Tailwind + Bootstrap Icons, current card/header language).

Coverage rule:

  • Every user-facing template in views/*.php must render views/partials/topbar-controls.php directly or through a shared header partial.

Non-Goals

  • Redesigning page layouts beyond what is needed to provide a consistent top bar.
  • Changing theme color tokens or dark/light palette values.
  • Altering authentication or route behavior.

Proposed Approach

1) Shared top-bar controls partial

Create a reusable partial dedicated to top-bar action controls. This partial will render:

  • Theme toggle button (always present).
  • Settings link (conditionally present).

Ordering rule:

  • Theme toggle appears immediately to the left of settings when settings is shown.

This keeps the placement contract explicit and reusable across all pages.

Interface contract

  • Partial path: views/partials/topbar-controls.php.
  • Include API:
    • $showSettings (bool): whether to render settings link in this header; default false inside partial.
    • $settingsUrl (string): target URL when settings is rendered; default is applied inside partial as url('/settings') when caller does not set it.
  • Required markup contract inside the partial:
    • Toggle button id: themeToggle.
    • Toggle icon id: themeToggleIcon.
    • Server-rendered baseline attributes:
      • aria-label="Switch to dark mode"
      • title="Switch to dark mode"
      • aria-pressed="false"
      • icon class starts at bi bi-moon-stars-fill
    • Toggle appears first in the controls row; settings link appears second when enabled.
  • Script contract:
    • views/partials/theme.php body script binds to #themeToggle and #themeToggleIcon.
    • If either element is missing, script exits without errors.

Responsibility split

  • views/partials/topbar-controls.php owns toggle markup, baseline attributes, ordering, optional settings link, and <noscript> rule that hides #themeToggle when JS is disabled.
  • views/partials/theme.php owns theme token CSS, initial theme resolution, cookie persistence, and click behavior synchronization.

2) Move toggle rendering responsibility

Adjust views/partials/theme.php so it no longer emits floating button markup and fixed-position CSS. Keep it focused on:

  • Theme pre-hydration script in <head>.
  • Theme utility classes/variables.
  • Toggle behavior script in <body> that binds to the top-bar toggle element.

For robust reuse, the behavior script should target stable element IDs/classes used by the shared controls partial and fail gracefully if a toggle is temporarily absent.

3) Standardize top bars across all views

Use a top-bar/header region in every view and include the shared controls partial:

  • Authenticated pages that already have a header (home/settings/vehicle-related views): insert shared controls in current header area.
  • login and setup: add a slim header at the top with app branding on the left and shared controls on the right.

This ensures the toggle is always visible and consistently located.

Header wrapper ownership:

  • To keep scope focused, slim header markup will be duplicated in login, setup, and export rather than introducing another new wrapper partial.

View coverage matrix

  • views/index.php: existing header; include topbar-controls with $showSettings = true.
  • views/vehicle.php: existing header; include topbar-controls with $showSettings = true.
  • views/settings.php: existing header; include topbar-controls with $showSettings = false (avoid self-link duplication).
  • views/login.php: add slim header; include topbar-controls with $showSettings = false.
  • views/setup.php: add slim header; include topbar-controls with $showSettings = false.
  • views/export.php: add theme partial include + slim top bar with topbar-controls and $showSettings = false, so toggle remains available in export view too.

Login/setup/export layout contract:

  • Body changes from full-screen centered container to vertical layout:
    • top header (theme-surface, app name left, controls right)
    • main content wrapper below header
  • Main wrapper keeps current centered card behavior using a flex container with min-height calculated to preserve vertical centering beneath header.

Data Flow and Interaction

Theme state flow remains unchanged:

  1. Initial theme resolved from cookie or system preference in head script.
  2. HTML root class/attribute updated (dark, data-theme).
  3. Toggle click flips theme, writes cookie, and synchronizes icon + accessibility attributes.

Only UI location changes; no persistence or resolution logic changes are introduced.

Accessibility and UX Requirements

  • Keep aria-label and aria-pressed synchronized with active theme.
  • Preserve keyboard focus visibility (:focus-visible ring).
  • Keep descriptive button title for pointer users.
  • No-JS rule: hide the toggle button via <noscript> style in topbar-controls.php (button is not shown without scripting). Visibility/usability requirements apply to standard JS-enabled runtime.

Error Handling

  • If cookie write fails (restricted storage), visual theme switching still works for current session.
  • If a page omits controls unexpectedly, script exits safely without throwing.

Fallback contract for cookie failures:

  • During current page/session: toggle applies visual theme immediately.
  • After reload/navigation: persisted preference may be unavailable; theme resolves again from cookie/system preference.
  • No blocking alert/toast is added in this change.

Verification Plan

Automated checks

  • Run php -l on every changed PHP file.

Manual checks

  • Confirm top-bar toggle presence and operation on:
    • Home (/home)
    • Vehicle detail pages (/vehicles/:id)
    • Settings (/settings)
    • Login (/login)
    • Setup (/setup)
    • Export (/vehicles/:id/export/html via /vehicles/:id/export/{format} route)
  • Confirm placement: toggle is left of settings where settings exists.
  • Confirm responsive behavior on mobile and desktop.
  • Confirm theme persistence after navigation and reload.
  • Confirm cookie-write failure fallback expectation: in-session switch works; reload may revert when cookie cannot be stored.
  • Confirm route-to-view coverage by checking all templates in views/*.php include topbar-controls.php (currently: index, vehicle, settings, login, setup, export).

Risks and Mitigations

  • Risk: Header markup variance across views could cause inconsistent placement.
    • Mitigation: Centralize controls in one partial and include it in all headers.
  • Risk: Existing fixed-position CSS may continue affecting layout.
    • Mitigation: Remove obsolete #themeToggle fixed positioning rules during migration.

Rollout Notes

  • Scope is a UI placement refactor with no database changes.
  • No migration steps required.