Files
initiative/docs/agents/research/2026-03-13-css-classes-buttons-hover.md
Lukas f9ef64bb00 Unify hover effects via semantic theme tokens
Replace one-off hover colors with hover-neutral/hover-destructive tokens
so all interactive elements respond consistently to theme changes. Fix
hover-neutral-bg token value (was identical to card surface, making hover
invisible on card backgrounds) to a semi-transparent primary tint. Switch
turn nav buttons to outline variant for visible hover feedback. Convert HP
popover damage/heal to plain buttons to avoid ghost variant hover conflict
with tailwind-merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:58:01 +01:00

11 KiB

date, git_commit, branch, topic, tags, status
date git_commit branch topic tags status
2026-03-13T15:35:07.699570+00:00 bd39808000 main CSS class usage, button categorization, and hover effects across all components
research
codebase
css
tailwind
buttons
hover
ui
complete

Research: CSS Class Usage, Button Categorization, and Hover Effects

Research Question

How are CSS classes used across all components? How are buttons categorized — are there primary and secondary buttons? What hover effects exist, and are they unified?

Summary

The project uses Tailwind CSS v4 with a custom dark theme defined in index.css via @theme. All class merging goes through a cn() utility (clsx + tailwind-merge). Buttons are built on a shared Button component using class-variance-authority (CVA) with three variants: default (primary), outline, and ghost. Hover effects are partially unified through semantic color tokens (hover-neutral, hover-action, hover-destructive) defined in the theme, but several components use one-off hardcoded hover colors that bypass the token system.

Detailed Findings

Theme System (index.css)

All colors are defined as CSS custom properties via Tailwind v4's @theme directive (index.css:3-26):

Token Value Purpose
--color-background #0f172a Page background
--color-foreground #e2e8f0 Default text
--color-muted #64748b Subdued elements
--color-muted-foreground #94a3b8 Secondary text
--color-card #1e293b Card/panel surfaces
--color-border #334155 Borders
--color-primary #3b82f6 Primary actions (blue)
--color-accent #3b82f6 Accent (same as primary)
--color-destructive #ef4444 Destructive actions (red)

Hover tokens (semantic layer for hover states):

Token Resolves to Usage
hover-neutral primary (blue) Text color on neutral hover
hover-action primary (blue) Text color on action hover
hover-destructive destructive (red) Text color on destructive hover
hover-neutral-bg card (slate) Background on neutral hover
hover-action-bg muted Background on action hover
hover-destructive-bg transparent Background on destructive hover

Button Component (components/ui/button.tsx)

Uses CVA with three variants and three sizes:

Variants:

Variant Base styles Hover
default (primary) bg-primary text-primary-foreground hover:bg-primary/90
outline border border-border bg-transparent hover:bg-hover-neutral-bg hover:text-hover-neutral
ghost (no background/border) hover:bg-hover-neutral-bg hover:text-hover-neutral

Sizes:

Size Classes
default h-9 px-4 py-2
sm h-8 px-3 text-xs
icon h-8 w-8

All variants share: rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50.

There is no "secondary" variant — the outline variant is the closest equivalent.

Composite Button Components

ConfirmButton (components/ui/confirm-button.tsx):

  • Wraps Button variant="ghost" size="icon"
  • Default state: hover:text-hover-destructive (uses token)
  • Confirming state: bg-destructive text-primary-foreground animate-confirm-pulse hover:bg-destructive hover:text-primary-foreground

OverflowMenu (components/ui/overflow-menu.tsx):

  • Trigger: Button variant="ghost" size="icon" with text-muted-foreground hover:text-hover-neutral
  • Menu items: raw <button> elements with hover:bg-muted/20 (not using the token system)

Button Usage Across Components

Component Button type Variant/Style
action-bar.tsx:556 <Button type="submit"> default (primary) — "Add"
action-bar.tsx:561 <Button type="button"> default (primary) — "Roll all"
turn-navigation.tsx:25,54 <Button size="icon"> default — prev/next turn
turn-navigation.tsx:47 <ConfirmButton> ghost+destructive — clear encounter
source-fetch-prompt.tsx:91 <Button size="sm"> default — "Load"
source-fetch-prompt.tsx:106 <Button size="sm" variant="outline"> outline — "Upload file"
bulk-import-prompt.tsx:31,45,106 <Button size="sm"> default — "Done"/"Load All"
source-manager.tsx:50 <Button size="sm" variant="outline"> outline — "Clear all"
hp-adjust-popover.tsx:112 <Button variant="ghost" size="icon"> ghost + custom red — damage
hp-adjust-popover.tsx:124 <Button variant="ghost" size="icon"> ghost + custom green — heal
player-management.tsx:67 <Button> default — "Create first player"
player-management.tsx:113 <Button variant="ghost"> ghost — "Add player"
create-player-modal.tsx:177 <Button variant="ghost"> ghost — "Cancel"
create-player-modal.tsx:180 <Button type="submit"> default — "Save"/"Create"
combatant-row.tsx:625 <ConfirmButton> ghost+destructive — remove combatant

Raw <button> elements (not using the Button component):

  • action-bar.tsx — suggestion items, count increment/decrement, browse toggle, custom add (all inline-styled)
  • combatant-row.tsx — editable name, HP display, AC, initiative, concentration toggle
  • stat-block-panel.tsx — fold/close/pin/unpin buttons
  • condition-picker.tsx — condition items
  • condition-tags.tsx — condition tags, add condition button
  • toast.tsx — dismiss button
  • player-management.tsx — close modal, edit player
  • create-player-modal.tsx — close modal
  • color-palette.tsx — color swatches
  • icon-grid.tsx — icon options

Hover Effects Inventory

Using semantic tokens (unified):

Hover class Meaning Used in
hover:bg-hover-neutral-bg Neutral background highlight button.tsx (outline/ghost), action-bar.tsx, condition-picker.tsx, condition-tags.tsx
hover:text-hover-neutral Text turns primary blue button.tsx (outline/ghost), action-bar.tsx, combatant-row.tsx, stat-block-panel.tsx, ac-shield.tsx, toast.tsx, overflow-menu.tsx, condition-tags.tsx
hover:text-hover-action Action text (same as neutral) action-bar.tsx (overflow trigger)
hover:text-hover-destructive Destructive text turns red confirm-button.tsx, source-manager.tsx
hover:bg-hover-destructive-bg Destructive background (transparent) source-manager.tsx

One-off / hardcoded hover colors (NOT using tokens):

Hover class Used in Context
hover:bg-primary/90 button.tsx (default variant) Primary button darken
hover:bg-accent/20 action-bar.tsx Suggestion highlight, custom add
hover:bg-accent/40 action-bar.tsx Count +/- buttons, confirm queued
hover:bg-muted/20 overflow-menu.tsx Menu item highlight
hover:bg-red-950 hp-adjust-popover.tsx Damage button
hover:text-red-300 hp-adjust-popover.tsx Damage button text
hover:bg-emerald-950 hp-adjust-popover.tsx Heal button
hover:text-emerald-300 hp-adjust-popover.tsx Heal button text
hover:text-foreground player-management.tsx, create-player-modal.tsx, icon-grid.tsx Close/edit buttons
hover:bg-background/50 player-management.tsx Player row hover
hover:bg-card icon-grid.tsx Icon option hover
hover:border-hover-destructive source-manager.tsx Clear all button border
hover:scale-110 color-palette.tsx Color swatch enlarge
hover:bg-destructive confirm-button.tsx (confirming state) Maintain red bg on hover
hover:text-primary-foreground confirm-button.tsx (confirming state) Maintain white text on hover

Hover unification assessment

The hover token system (hover-neutral, hover-action, hover-destructive) provides a consistent pattern for the most common interactions. The Button component's outline and ghost variants use these tokens, and many inline buttons in action-bar, combatant-row, stat-block-panel, and condition components also use them.

However, there are notable gaps:

  1. HP adjust popover uses hardcoded red/green colors (red-950, emerald-950) instead of tokens
  2. Overflow menu items use hover:bg-muted/20 instead of hover:bg-hover-neutral-bg
  3. Player management modals use hover:text-foreground and hover:bg-background/50 instead of the semantic tokens
  4. Action-bar suggestion items use hover:bg-accent/20 and hover:bg-accent/40 — accent-specific patterns not in the token system
  5. Icon grid and color palette use their own hover patterns (hover:bg-card, hover:scale-110)

Code References

  • apps/web/src/index.css:3-26 — Theme color definitions including hover tokens
  • apps/web/src/components/ui/button.tsx:1-38 — Button component with CVA variants
  • apps/web/src/components/ui/confirm-button.tsx:93-115 — ConfirmButton with destructive hover states
  • apps/web/src/components/ui/overflow-menu.tsx:38-72 — OverflowMenu with non-token hover
  • apps/web/src/components/hp-adjust-popover.tsx:117-129 — Hardcoded red/green hover colors
  • apps/web/src/components/action-bar.tsx:80-188 — Mixed token and accent-based hovers
  • apps/web/src/components/combatant-row.tsx:147-629 — Inline buttons with token hovers
  • apps/web/src/components/player-management.tsx:58-98 — Non-token hover patterns
  • apps/web/src/components/stat-block-panel.tsx:55-109 — Consistent token usage
  • apps/web/src/lib/utils.ts:1-5cn() utility (clsx + twMerge)

Architecture Documentation

The styling architecture follows this pattern:

  1. Theme layer: index.css defines all color tokens via @theme, including semantic hover tokens
  2. Component layer: Button (CVA) provides the shared button abstraction with three variants
  3. Composite layer: ConfirmButton and OverflowMenu wrap Button with additional behavior
  4. Usage layer: Components use either Button component or raw <button> elements with inline Tailwind classes

The cn() utility from lib/utils.ts is used in 9+ components for conditional class merging.

Custom animations are defined in index.css via @keyframes + @utility pairs: slide-in-right, confirm-pulse, settle-to-bottom, rise-to-center, slide-down-in, slide-up-out, concentration-pulse.

Open Questions

  1. The hover-action and hover-action-bg tokens are defined but rarely used — hover-action appears only once in action-bar.tsx:565. Is this intentional or an incomplete migration?
  2. The accent color (#3b82f6) is identical to primary — are they intended to diverge in the future, or is this redundancy?
  3. Should the hardcoded HP adjust colors (red/emerald) be promoted to theme tokens (e.g., hover-damage, hover-heal)?