Files
astro-website/openspec/changes/archive/2026-02-11-remember-theme/design.md
Santhosh Janardhanan f50a828535
Some checks failed
ci / site (push) Has been cancelled
publish-image / publish (push) Has been cancelled
Now I remember the theme
2026-02-10 20:38:38 -05:00

63 lines
3.4 KiB
Markdown

## Context
The site already supports theme selection via a floating notch and persists the user's choice using `localStorage` (key: `site.theme`). Theme selection affects the root document via `html[data-theme]` and is initialized before first paint via an inline head script.
Two gaps remain:
- Returning users should reliably see their last-selected theme even in environments where `localStorage` is unavailable.
- We need analytics to measure theme switcher usage and preferred themes.
The site uses Umami for analytics. Most interactions are tracked via `data-umami-event*` attributes, and runtime-only events use `window.umami.track(...)`.
## Goals / Non-Goals
**Goals:**
- Persist theme selection across visits with a robust fallback: `localStorage` primary, client-side cookie fallback.
- Apply the stored theme before first paint when possible.
- Emit a deterministic Umami event when a user changes theme via the theme notch.
**Non-Goals:**
- Server-side rendering of theme choice (the site is statically built; no request-time HTML variation).
- Tracking theme selection on initial page load (only user-initiated changes).
- Adding new UI beyond the existing theme notch.
## Decisions
1) Persistence mechanism and precedence
- **Decision**: Read theme preference in this order:
1. `localStorage` (`site.theme`)
2. Cookie (`site_theme`)
3. Environment signals (forced-colors, prefers-color-scheme)
- **Rationale**: `localStorage` is already in use and provides a stable primary store. Cookies provide a resilient fallback when storage access is blocked or throws.
- **Alternatives considered**:
- Cookie-only: simpler but unnecessary regression from existing behavior.
- URL param: not persistent and adds user-visible noise.
2) Cookie format and attributes
- **Decision**: Store `site_theme=<theme>` with `Max-Age=31536000`, `Path=/`, `SameSite=Lax`. Set `Secure` when running under HTTPS.
- **Rationale**: First-party cookie with long TTL provides continuity across visits. The cookie is readable from the inline head script for pre-paint initialization.
3) Analytics event shape
- **Decision**: Emit a custom Umami event via `window.umami.track("theme_switch", data)` only on user-initiated changes.
- **Event properties**:
- `target_id`: `theme.switch.<theme>`
- `placement`: `theme_notch`
- `theme`: new theme value (`dark` | `light` | `high-contrast`)
- `prev_theme`: previous theme value (same enum) when known
- **Rationale**: A dedicated event name makes reporting straightforward (no need to filter general `click`). Using `target_id`/`placement` keeps it compatible with the site's interaction taxonomy.
- **Alternatives considered**:
- Reuse `click` event: consistent, but mixes preference changes into general click reporting.
4) Avoid tracking initial theme restoration
- **Decision**: Do not emit `theme_switch` from the head theme-init script.
- **Rationale**: We want to measure explicit user interaction with the notch, not implicit restoration.
## Risks / Trade-offs
- Cookie and storage may both be blocked in restrictive environments → fallback to environment signals; no persistence.
- Umami may be disabled/unconfigured or not loaded at event time → guard with `typeof window.umami !== "undefined"` and keep behavior non-fatal.
- Using cookies introduces another persistence layer → must keep `localStorage` and cookie consistent on successful theme changes.