Implement the 028-semantic-hover-tokens feature that unifies hover colors across all interactive UI components via six CSS custom property tokens (three text, three background) defined in the Tailwind v4 theme, replacing hardcoded hover classes in 9 component files plus the shared Button primitive with semantic token references so all hover colors can be globally reconfigured from one place

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-10 19:50:22 +01:00
parent f029c1a85b
commit 99d1ba1bcd
18 changed files with 542 additions and 23 deletions

View File

@@ -0,0 +1,83 @@
# Research: Semantic Hover Tokens
## R-001: Tailwind v4 Theme Token Mechanism
**Decision**: Define new CSS custom properties in the `@theme` block of `index.css`. Tailwind v4 automatically makes `--color-*` variables available as utility classes (e.g., `--color-hover-neutral``text-hover-neutral`, `bg-hover-neutral`).
**Rationale**: The project already uses this pattern for `--color-primary`, `--color-destructive`, etc. Adding new tokens follows the established convention and requires zero config changes.
**Alternatives considered**:
- Tailwind plugin extending theme: Unnecessary — Tailwind v4 CSS-native theming handles this.
- CSS-only approach without Tailwind utilities: Would lose the `hover:text-*` class syntax that all components already use.
## R-002: Scope of Affected Components
**Decision**: The spec lists five components, but the audit found hover colors in additional files: `hp-adjust-popover.tsx`, `stat-block-panel.tsx`, `bestiary-search.tsx`, `ac-shield.tsx`, and `ui/button.tsx`. The plan will address the five specified components plus `ac-shield.tsx` (already uses `hover:text-primary` for an editable field). The HP adjust popover's semantic damage/healing colors are **out of scope** — they represent domain-semantic colors (damage=red, healing=green) rather than interaction-tier hover colors. The `button.tsx` CVA variants were initially out of scope but were brought in during implementation (see R-007).
**Rationale**: The HP popover buttons communicate game semantics (damage vs. healing), not interaction intent. Forcing them into the three-tier system would reduce clarity. `stat-block-panel.tsx` and `bestiary-search.tsx` use `hover:text-foreground` which already matches the neutral-interactive intent (dim→bright) — these can adopt the neutral token.
**Alternatives considered**:
- Include all hover colors across all components: Over-scoped; HP popover serves a different purpose.
- Strictly limit to five listed files: Would miss `ac-shield.tsx` which is clearly neutral-interactive.
## R-003: Token Naming Convention
**Decision**: Use `--color-hover-neutral`, `--color-hover-action`, `--color-hover-destructive` as the CSS custom property names. This yields Tailwind classes like `hover:text-hover-neutral`, `hover:bg-hover-action`, etc.
**Rationale**: The `hover-` prefix groups them visually in the theme. The tier names (neutral/action/destructive) are concise and parallel the existing `--color-primary`/`--color-destructive` naming.
**Alternatives considered**:
- `--color-interactive-*`: Longer, and "interactive" is redundant since hover already implies interaction.
- `--color-hover-primary` for the action tier: Conflicts conceptually with existing `--color-primary` (which is used for non-hover purposes too).
## R-004: Background Hover Variants
**Decision**: Define three additional background tokens: `--color-hover-neutral-bg`, `--color-hover-action-bg`, `--color-hover-destructive-bg`. Components that use `hover:bg-*` will reference these.
**Rationale**: Several components use background hovers (condition-picker `hover:bg-card`, navigation buttons `hover:bg-muted`, bestiary items `hover:bg-accent/10`). A single text-only token won't cover these cases.
**Alternatives considered**:
- Reuse existing `--color-card`/`--color-muted` for background hovers: Breaks the single-point-of-change goal — changing the neutral hover background would also change all card backgrounds.
- No background tokens (text-only): Would leave background hovers hardcoded, defeating the purpose.
## R-005: Default Token Values
**Decision**: Map defaults to preserve current visual appearance:
- `--color-hover-neutral: var(--color-primary)` (#3b82f6 — blue, matching previous `hover:text-primary` behavior)
- `--color-hover-action: var(--color-primary)` (#3b82f6 — blue)
- `--color-hover-destructive: var(--color-destructive)` (#ef4444 — red)
- `--color-hover-neutral-bg: var(--color-card)` (#1e293b)
- `--color-hover-action-bg: var(--color-muted)` (#64748b — defined but not used by nav buttons, see R-008)
- `--color-hover-destructive-bg: transparent` (current destructive hovers are text-only in scope)
**Rationale**: Using `var()` references means the hover tokens inherit from the base theme by default, reducing duplication. The neutral token was initially `var(--color-foreground)` but changed to `var(--color-primary)` during implementation because elements already at `text-foreground` (name, initiative, HP) had no visible hover change — the hover target color was identical to the resting color. Using `var(--color-primary)` preserves the original blue hover feedback while still centralizing control.
**Alternatives considered**:
- Hardcode hex values: Would break the cascade if base theme colors change.
- Fewer background tokens: The action and neutral backgrounds serve different purposes currently.
- `--color-hover-neutral: var(--color-foreground)`: No visible hover for elements already at foreground color — rejected during implementation.
## R-006: Components Using hover:text-foreground
**Decision**: `stat-block-panel.tsx` and `bestiary-search.tsx` close buttons use `hover:text-foreground` (dim→bright). These will migrate to `hover:text-hover-neutral` since the default value of `--color-hover-neutral` is `var(--color-primary)` — providing a blue hover instead of the previous dim→bright.
**Rationale**: Consistent with the neutral-interactive tier. These are close/dismiss actions, not destructive (they don't delete data).
## R-007: Button Component (ui/button.tsx) Brought Into Scope
**Decision**: Migrate the `outline` and `ghost` CVA variant hover styles in `ui/button.tsx` from hardcoded `hover:bg-card hover:text-foreground` to `hover:bg-hover-neutral-bg hover:text-hover-neutral`.
**Rationale**: Initially excluded as a shared UI primitive (R-002). During implementation, the ghost variant's `hover:text-foreground` produced no visible hover on the search button (same issue as R-005 — foreground→foreground). Additionally, the outline variant's `hover:bg-card` conflicted with inline `hover:bg-hover-action-bg` on the nav buttons, creating a double-background hover artifact. Migrating the Button variants to semantic tokens resolved both issues.
**Alternatives considered**:
- Keep button.tsx out of scope: Would leave the search button without visible hover and nav buttons with conflicting hover backgrounds.
## R-008: Nav Button Background Hover Removed
**Decision**: The Previous/Next outline buttons in `turn-navigation.tsx` use `hover:bg-transparent` instead of `hover:bg-hover-action-bg`. The blue text + blue border provide sufficient hover feedback without a background fill.
**Rationale**: The `--color-hover-action-bg` default (`var(--color-muted)` = `#64748b`) created a heavy filled-box appearance on the small outline icon buttons. Combined with the blue border and icon, the solid grey background was visually overwhelming. Removing it produces a cleaner hover state.
**Alternatives considered**:
- Keep `hover:bg-hover-action-bg`: Visually heavy on small outline buttons.
- Use a semi-transparent background: Adds complexity for marginal benefit.