# SEO Theme Support Update
**Date:** 2026-03-21
**Agent:** seo-specialist
**Task:** Update SEO metadata for theme support
---
## Summary of Changes
### 1. `src/layouts/BaseLayout.astro` — `theme-color` meta tags
**Before:**
```html
```
**After:**
```html
```
The dual `media` attribute approach lets browsers natively use the correct browser-chrome color based on OS theme — even before JavaScript runs. This is important for users who haven't yet interacted with the site.
The inline theme-init script (already present for FOUC prevention) was extended to also sync these meta tags when a user has a **stored explicit preference** in localStorage that overrides their OS setting.
---
### 2. `src/components/ThemeToggle.astro` — Meta tag sync on toggle
**Before:** `applyTheme()` used `querySelector` (selects only the first meta), and the system-preference change listener did not update theme-color at all.
**After:**
- `applyTheme()` uses `querySelectorAll('meta[name="theme-color"]').forEach(...)` to update **all** theme-color metas simultaneously
- System preference change listener (`matchMedia` change event) now also syncs all theme-color metas when no stored preference exists
---
## What Was NOT Changed (and Why)
### Structured Data (JSON-LD)
All schema.org structured data in `BaseLayout.astro` and `SEO.astro` is **theme-agnostic by design**. JSON-LD describes content/entities, not visual presentation. Search engines and AI crawlers have no concept of visual theme when consuming structured data. No changes needed.
### Open Graph / Twitter Card Images
Social preview images (`og-image.jpg`) are static assets served to social crawlers and link-unfurling bots. These crawlers:
- Do not respect OS `prefers-color-scheme`
- Do not execute JavaScript
- Do not honor `media` query attributes
Providing separate dark/light OG images would require serving different `` values based on the server-side request context, which adds complexity without meaningful SEO benefit. The existing `og-image.jpg` works universally.
### Other Meta Tags
All other meta tags (title, description, canonical, robots, OG/Twitter properties) are theme-agnostic and correct as-is.
---
## Color Values Used
| Theme | `theme-color` value | Token |
|-------|-------------------|-------|
| Light | `#0891b2` | `--palette-primary-500` (brand cyan) |
| Dark | `#0f172a` | `--color-surface` dark (deep navy) |
These values match the design tokens in `src/styles/design-tokens.css`.
---
## How It Works End-to-End
1. **Page load (no JS yet):** Browser reads both `theme-color` meta tags and picks the one matching OS preference via the `media` attribute — instant, no flash.
2. **Inline init script fires:** If user has a stored preference that differs from OS, the script updates both meta tag `content` values to the stored preference color.
3. **User clicks ThemeToggle:** `applyTheme()` toggles the `.dark` class AND updates all `theme-color` metas in one synchronous call.
4. **User changes OS theme (no stored preference):** `matchMedia` change listener updates `.dark` class AND syncs all theme-color metas.
---
## Testing
Verify correct behavior by:
1. Opening DevTools → Elements → search for `theme-color` meta tags
2. Toggle between light/dark using the site toggle — both meta `content` values should update to the same color
3. Remove localStorage `theme` key, change OS theme — meta tags should track OS preference
4. Check browser tab/address bar color updates on Chrome/Edge (Android) after toggle