Add nav-sitename, nav-description, nav-toggle colour keys

Separates sidebar header colours from nav link colours. The three new keys
control the site name, site description, and dark/light toggle independently,
each cascading from the nearest nav variable (nav-sitename → nav-link,
nav-description and nav-toggle → nav-section-heading) so existing themes
are unaffected.

Enables Claude Design to fine-tune sidebar header legibility on saturated or
bold nav backgrounds without having to override the nav link colours, and vice
versa. CSS selectors and applyThemeYml() updated; app/theme.yml, CLAUDE.md,
and docs/claude-design.md updated with full key reference and pattern examples.

https://claude.ai/code/session_01NQKywehSj8Ku4yKhwB4VNB
This commit is contained in:
Claude 2026-05-18 14:47:28 +00:00
parent 1d76226311
commit f4a41ed3ae
No known key found for this signature in database
4 changed files with 77 additions and 31 deletions

View file

@ -233,6 +233,9 @@ Presentational config separate from `config.yml`. Controls accent colour, dark/l
| `nav-link` | `--nav-link-colour` | falls back to `text` | | `nav-link` | `--nav-link-colour` | falls back to `text` |
| `nav-link-active` | `--nav-link-active-colour` | falls back to `accent` | | `nav-link-active` | `--nav-link-active-colour` | falls back to `accent` |
| `nav-section-heading` | `--nav-section-heading-colour` | falls back to `text-muted` | | `nav-section-heading` | `--nav-section-heading-colour` | falls back to `text-muted` |
| `nav-sitename` | `--nav-sitename-colour` | falls back to `nav-link` |
| `nav-description` | `--nav-description-colour` | falls back to `nav-section-heading` |
| `nav-toggle` | `--nav-toggle-colour` | falls back to `nav-section-heading` |
| `divider` | `--divider` | `color-mix(in srgb, background 85%, text)` | | `divider` | `--divider` | `color-mix(in srgb, background 85%, text)` |
**When to use nav-link keys:** When `nav-background` matches or is very close to `accent`, the default behaviour (active link coloured with `accent`) makes links invisible. Set `nav-link`, `nav-link-active`, and `nav-section-heading` explicitly so all three are legible against `nav-background`. Example: a red nav background needs white (`#FFFFFF`) for all three nav colour keys. **When to use nav-link keys:** When `nav-background` matches or is very close to `accent`, the default behaviour (active link coloured with `accent`) makes links invisible. Set `nav-link`, `nav-link-active`, and `nav-section-heading` explicitly so all three are legible against `nav-background`. Example: a red nav background needs white (`#FFFFFF`) for all three nav colour keys.

View file

@ -59,6 +59,9 @@ if ('serviceWorker' in navigator) {
--nav-link-colour: var(--nav-font-colour); --nav-link-colour: var(--nav-font-colour);
--nav-link-active-colour: var(--accent); --nav-link-active-colour: var(--accent);
--nav-section-heading-colour: var(--font-colour-muted); --nav-section-heading-colour: var(--font-colour-muted);
--nav-sitename-colour: var(--nav-link-colour);
--nav-description-colour: var(--nav-section-heading-colour);
--nav-toggle-colour: var(--nav-section-heading-colour);
--nav-active-bg: rgba(var(--accent-rgb), 0.10); --nav-active-bg: rgba(var(--accent-rgb), 0.10);
--nav-hover-bg: rgba(var(--accent-rgb), 0.05); --nav-hover-bg: rgba(var(--accent-rgb), 0.05);
--font-colour: #1E293B; --font-colour: #1E293B;
@ -91,6 +94,9 @@ if ('serviceWorker' in navigator) {
--bg-nav: #1E293B; --bg-nav: #1E293B;
--nav-font-colour: #E2E8F0; --nav-font-colour: #E2E8F0;
--nav-link-colour: var(--nav-font-colour); --nav-link-colour: var(--nav-font-colour);
--nav-sitename-colour: var(--nav-link-colour);
--nav-description-colour: var(--nav-section-heading-colour);
--nav-toggle-colour: var(--nav-section-heading-colour);
--nav-link-active-colour: var(--accent); --nav-link-active-colour: var(--accent);
--nav-section-heading-colour: var(--font-colour-muted); --nav-section-heading-colour: var(--font-colour-muted);
--nav-active-bg: rgba(96, 165, 250, 0.15); --nav-active-bg: rgba(96, 165, 250, 0.15);
@ -196,14 +202,14 @@ body {
font-family: var(--font-title); font-family: var(--font-title);
font-weight: var(--font-title-weight); font-weight: var(--font-title-weight);
font-size: 1.15rem; font-size: 1.15rem;
color: var(--nav-link-colour, var(--font-colour)); color: var(--nav-sitename-colour);
line-height: 1.3; line-height: 1.3;
text-decoration: none; text-decoration: none;
display: block; display: block;
} }
.sidebar-sitename:hover { color: var(--nav-link-active-colour, var(--accent)); } .sidebar-sitename:hover { color: var(--nav-link-active-colour, var(--accent)); }
.sidebar-description { font-size: 0.8rem; color: var(--nav-section-heading-colour, var(--font-colour-muted)); margin-top: 0.25rem; line-height: 1.4; } .sidebar-description { font-size: 0.8rem; color: var(--nav-description-colour); margin-top: 0.25rem; line-height: 1.4; }
/* Search */ /* Search */
.search-container { padding: 0.75rem 1.25rem; flex-shrink: 0; } .search-container { padding: 0.75rem 1.25rem; flex-shrink: 0; }
@ -341,9 +347,9 @@ body {
gap: 0.5rem; gap: 0.5rem;
padding: 0.4rem 0.6rem; padding: 0.4rem 0.6rem;
font-size: 0.8rem; font-size: 0.8rem;
color: var(--nav-section-heading-colour, var(--font-colour-muted)); color: var(--nav-toggle-colour);
background: none; background: none;
border: 1px solid color-mix(in srgb, var(--bg-nav) 70%, var(--nav-link-colour, var(--font-colour))); border: 1px solid color-mix(in srgb, var(--bg-nav) 70%, var(--nav-toggle-colour));
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-family: var(--font-body); font-family: var(--font-body);
@ -351,7 +357,7 @@ body {
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
.theme-toggle:hover { color: var(--nav-link-colour, var(--font-colour)); border-color: var(--nav-section-heading-colour, var(--font-colour-muted)); } .theme-toggle:hover { color: var(--nav-link-colour, var(--font-colour)); border-color: var(--nav-toggle-colour); }
.theme-toggle svg { width: 16px; height: 16px; flex-shrink: 0; } .theme-toggle svg { width: 16px; height: 16px; flex-shrink: 0; }
/* ═══════════════════════════════════════════ /* ═══════════════════════════════════════════
@ -1259,6 +1265,9 @@ body {
if (m['nav-link']) vars.push(`--nav-link-colour: ${m['nav-link']}`); if (m['nav-link']) vars.push(`--nav-link-colour: ${m['nav-link']}`);
if (m['nav-link-active']) vars.push(`--nav-link-active-colour: ${m['nav-link-active']}`); if (m['nav-link-active']) vars.push(`--nav-link-active-colour: ${m['nav-link-active']}`);
if (m['nav-section-heading']) vars.push(`--nav-section-heading-colour: ${m['nav-section-heading']}`); if (m['nav-section-heading']) vars.push(`--nav-section-heading-colour: ${m['nav-section-heading']}`);
if (m['nav-sitename']) vars.push(`--nav-sitename-colour: ${m['nav-sitename']}`);
if (m['nav-description']) vars.push(`--nav-description-colour: ${m['nav-description']}`);
if (m['nav-toggle']) vars.push(`--nav-toggle-colour: ${m['nav-toggle']}`);
if (m['divider']) vars.push(`--divider: ${m['divider']}`); if (m['divider']) vars.push(`--divider: ${m['divider']}`);
if (vars.length) modeCss += `:root[data-theme="${mode}"] { ${vars.join('; ')}; }\n`; if (vars.length) modeCss += `:root[data-theme="${mode}"] { ${vars.join('; ')}; }\n`;
}); });

View file

@ -15,6 +15,9 @@ light:
# nav-link: "#1E293B" # inactive nav link text (defaults to text) # nav-link: "#1E293B" # inactive nav link text (defaults to text)
# nav-link-active: "#2563EB" # active nav link text (defaults to accent) # nav-link-active: "#2563EB" # active nav link text (defaults to accent)
# nav-section-heading: "#64748B" # nav section label text (defaults to text-muted) # nav-section-heading: "#64748B" # nav section label text (defaults to text-muted)
# nav-sitename: "#1E293B" # site name in sidebar header (defaults to nav-link)
# nav-description: "#64748B" # site description in sidebar header (defaults to nav-section-heading)
# nav-toggle: "#64748B" # dark/light mode toggle (defaults to nav-section-heading)
# divider: "#CBD5E1" # border/hr colour (defaults to color-mix of background + text) # divider: "#CBD5E1" # border/hr colour (defaults to color-mix of background + text)
dark: dark:
@ -26,6 +29,9 @@ dark:
# nav-link: "#E2E8F0" # inactive nav link text (defaults to text) # nav-link: "#E2E8F0" # inactive nav link text (defaults to text)
# nav-link-active: "#60A5FA" # active nav link text (defaults to accent) # nav-link-active: "#60A5FA" # active nav link text (defaults to accent)
# nav-section-heading: "#94A3B8" # nav section label text (defaults to text-muted) # nav-section-heading: "#94A3B8" # nav section label text (defaults to text-muted)
# nav-sitename: "#E2E8F0" # site name in sidebar header (defaults to nav-link)
# nav-description: "#94A3B8" # site description in sidebar header (defaults to nav-section-heading)
# nav-toggle: "#94A3B8" # dark/light mode toggle (defaults to nav-section-heading)
# divider: "#334155" # border/hr colour (defaults to color-mix of background + text) # divider: "#334155" # border/hr colour (defaults to color-mix of background + text)
# ────────────────────────────────── # ──────────────────────────────────

View file

@ -24,6 +24,9 @@ light:
nav-link: "#1E293B" # inactive nav link text nav-link: "#1E293B" # inactive nav link text
nav-link-active: "#2563EB" # active (current page) nav link text nav-link-active: "#2563EB" # active (current page) nav link text
nav-section-heading: "#64748B" # nav section label text (uppercase, small) nav-section-heading: "#64748B" # nav section label text (uppercase, small)
nav-sitename: "#1E293B" # site name in sidebar header
nav-description: "#64748B" # site description below the site name
nav-toggle: "#64748B" # dark/light mode toggle button
# divider: "#CBD5E1" # omit to auto-derive via color-mix(background, text) # divider: "#CBD5E1" # omit to auto-derive via color-mix(background, text)
dark: dark:
@ -35,6 +38,9 @@ dark:
nav-link: "#E2E8F0" nav-link: "#E2E8F0"
nav-link-active: "#60A5FA" nav-link-active: "#60A5FA"
nav-section-heading: "#94A3B8" nav-section-heading: "#94A3B8"
nav-sitename: "#E2E8F0"
nav-description: "#94A3B8"
nav-toggle: "#94A3B8"
# divider: "#334155" # omit to auto-derive via color-mix(background, text) # divider: "#334155" # omit to auto-derive via color-mix(background, text)
# ────────────────────────────────── # ──────────────────────────────────
@ -95,38 +101,53 @@ nav-width: 20em
--- ---
## Critical rule: nav contrast ## Nav colour keys: when to set them
The renderer defaults `nav-link-active` to `accent` and `nav-link` to `text`. There are six nav colour keys divided into two groups:
When `nav-background` and `accent` share the same hue (or are very close),
active nav links become invisible — the coloured text disappears into a
coloured background.
The nav colour keys also control the **site name, site description, and **Nav links and labels** — control the navigation list itself:
dark/light mode toggle** — all three live inside the sidebar and inherit from - `nav-link` — inactive link text (defaults to `text`)
the same variables. On muted or neutral nav backgrounds the content-area - `nav-link-active` — active/current page link text (defaults to `accent`)
fallbacks (`text`, `text-muted`) are fine. On any saturated or bold nav - `nav-section-heading` — uppercase section labels (defaults to `text-muted`)
background the contrast between `text` and `text-muted` is likely too low for
these elements to remain legible, so all three nav colour keys must be set
explicitly.
**Rule of thumb:** if `nav-background` has a saturation above roughly 20 % or **Sidebar header elements** — control the branding area above the nav list:
a lightness below 30 % (dark sidebar) or above 85 % (near-white sidebar that - `nav-sitename` — site name (defaults to `nav-link`)
differs noticeably from the page background), set `nav-link`, - `nav-description` — subtitle below the site name (defaults to `nav-section-heading`)
`nav-link-active`, and `nav-section-heading` explicitly for that mode. - `nav-toggle` — dark/light mode toggle button (defaults to `nav-section-heading`)
**Always set all three nav colour keys explicitly** whenever `nav-background` ### When the defaults are fine
is anything other than a neutral near-white (light) or near-black (dark).
On themes where `nav-background` is a neutral near-white (light mode) or
near-black (dark mode), `text` and `text-muted` read well against the nav
background. All six keys can be omitted and the fallback chain works correctly.
### When to set the keys explicitly
Set all six keys whenever `nav-background` is anything other than a neutral:
any saturated brand colour (red, navy, forest green, teal), any noticeably
dark sidebar in an otherwise light design, or any light-but-tinted background.
The two groups can be set independently. On a subtly tinted nav where the
link defaults look fine but the site name needs slightly more weight or a
different shade, set only the header keys (`nav-sitename`, `nav-description`,
`nav-toggle`) and leave the nav link keys to their defaults.
**Rule of thumb:** if `nav-background` has saturation above ~20 % or lightness
below 30 % (dark sidebar) or differs from `background` by more than a slight
tint, set all six explicitly for that mode.
### Pattern: accent-coloured nav (e.g. brand red, navy, forest green) ### Pattern: accent-coloured nav (e.g. brand red, navy, forest green)
```yaml ```yaml
light: light:
accent: "#D00C33" accent: "#D00C33"
nav-background: "#D00C33" # same as accent — nav links MUST be overridden nav-background: "#D00C33" # same as accent — all nav keys must be set
nav-link: "#FFFFFF" nav-link: "#FFFFFF"
nav-link-active: "#FFFFFF" nav-link-active: "#FFFFFF"
nav-section-heading: "rgba(255,255,255,0.65)" nav-section-heading: "rgba(255,255,255,0.65)"
nav-sitename: "#FFFFFF"
nav-description: "rgba(255,255,255,0.65)"
nav-toggle: "rgba(255,255,255,0.65)"
dark: dark:
accent: "#D00C33" accent: "#D00C33"
@ -134,6 +155,9 @@ dark:
nav-link: "#E2E2E2" nav-link: "#E2E2E2"
nav-link-active: "#FFFFFF" nav-link-active: "#FFFFFF"
nav-section-heading: "#888888" nav-section-heading: "#888888"
nav-sitename: "#FFFFFF"
nav-description: "#888888"
nav-toggle: "#888888"
``` ```
### Pattern: dark nav in light mode (sidebar darker than content) ### Pattern: dark nav in light mode (sidebar darker than content)
@ -144,6 +168,9 @@ light:
nav-link: "#CBD5E1" nav-link: "#CBD5E1"
nav-link-active: "#FFFFFF" nav-link-active: "#FFFFFF"
nav-section-heading: "#64748B" nav-section-heading: "#64748B"
nav-sitename: "#FFFFFF"
nav-description: "#64748B"
nav-toggle: "#64748B"
``` ```
### Pattern: transparent / very light nav (default behaviour) ### Pattern: transparent / very light nav (default behaviour)
@ -189,9 +216,10 @@ uses its own per-callout colour settings rather than the semantic variables.
## Checklist before finalising a theme ## Checklist before finalising a theme
- [ ] `nav-link`, `nav-link-active`, `nav-section-heading` specified for both - [ ] All six nav colour keys (`nav-link`, `nav-link-active`, `nav-section-heading`,
`light` and `dark` whenever `nav-background` is non-neutral `nav-sitename`, `nav-description`, `nav-toggle`) set for both `light` and
- [ ] All three nav link colours contrast against `nav-background` (WCAG AA minimum) `dark` whenever `nav-background` is non-neutral
- [ ] All nav colours contrast against `nav-background` (WCAG AA minimum)
- [ ] `colours-semantic-dark` provided with lighter variants of each colour - [ ] `colours-semantic-dark` provided with lighter variants of each colour
- [ ] `callouts` `primary-colour` matches `colours-semantic` values for consistency - [ ] `callouts` `primary-colour` matches `colours-semantic` values for consistency
- [ ] `divider` omitted unless the auto-derived value looks wrong (check hr and table borders) - [ ] `divider` omitted unless the auto-derived value looks wrong (check hr and table borders)