---
phase: 13-design-system-foundation
plan: "02"
subsystem: backend/internal/web/ui
tags: [css-migration, multi-class-pattern, component-api, design-system]
dependency_graph:
  requires: [13-01]
  provides: [multi-class-button-css, typed-card-api, pill-badge-css, ghost-button-variant]
  affects:
    - backend/internal/web/ui/button.css
    - backend/internal/web/ui/button.templ
    - backend/internal/web/ui/badge.css
    - backend/internal/web/ui/card.css
    - backend/internal/web/ui/card.templ
    - backend/internal/web/ui/variants.go
    - backend/internal/web/ui/ui_test.go
    - backend/templates/planning.templ
    - backend/templates/tasks.templ
    - backend/templates/events.templ
    - backend/templates/etapes.templ
    - backend/templates/tablos.templ
    - backend/templates/auth_login.templ
    - backend/templates/auth_signup.templ
tech_stack:
  added: []
  patterns: [multi-class-css-compound-selectors, typed-templ-component-props, tdd-red-green]
key_files:
  created: []
  modified:
    - backend/internal/web/ui/variants.go
    - backend/internal/web/ui/button.templ
    - backend/internal/web/ui/button.css
    - backend/internal/web/ui/badge.css
    - backend/internal/web/ui/card.css
    - backend/internal/web/ui/card.templ
    - backend/internal/web/ui/ui_test.go
    - backend/templates/planning.templ
    - backend/templates/tasks.templ
    - backend/templates/events.templ
    - backend/templates/etapes.templ
    - backend/templates/tablos.templ
    - backend/templates/auth_login.templ
    - backend/templates/auth_signup.templ
decisions:
  - "TabloCard wraps ui.Card in a <div id=tablo-{id}> instead of passing id via Attrs — typed CardProps has no Attrs field; outer div preserves HTMX id-targeting without changing the delete/confirm zone selectors"
  - "auth_login.templ and auth_signup.templ extract loginCardBody/signupCardBody as private templ components — cleaner than ComponentFunc inline; matches the typed Props pattern"
  - "button.css adds .ui-button-soft.ui-button-neutral rule (not in go-backend) — original button.css had soft-neutral-md compound; must exist in new multi-class form to avoid visual regression"
metrics:
  duration: "~15 minutes"
  completed_date: "2026-05-16"
  tasks: 2
  files: 14
---

# Phase 13 Plan 02: Component API Migration — Multi-Class Button, Typed Card, New Variants Summary

Multi-class button CSS and ButtonClass() output migration (compound to multi-class), card.templ rewritten to typed Header/Body/Footer Props API, badge.css and card.css replaced with go-backend token-based versions, all 4 template files updated from compound to multi-class button class strings.

## Tasks Completed

| Task | Name | Commit | Key Files |
|------|------|--------|-----------|
| 1 | Update ButtonClass() multi-class + button.templ + ui_test.go button assertions | 66f23bb | variants.go, button.templ, ui_test.go |
| 2 | Replace CSS files, migrate card.templ, update template hardcodes | a30a6f9 | button.css, badge.css, card.css, card.templ, ui_test.go, 6 template files |

## What Was Built

**Task 1 — ButtonClass() multi-class migration (TDD):**
- Updated `ButtonClass()` in `variants.go` from compound format (`ui-button ui-button-solid-default-md`) to multi-class format (`ui-button ui-button-solid ui-button-default ui-button-md`).
- Ghost variant special case: omits tone class, emits `ui-button ui-button-ghost ui-button-md`.
- Updated `button.templ`: added `Icon string` field to `ButtonProps`, replaced inline `btnType` variable with `buttonType(props.Type)` helper from `helpers.go`.
- Updated `ui_test.go`: `TestButton_DefaultSolidMD` now uses multi-class slice assertion pattern; `TestButtonClass_String` updated to new expected string; `TestButtonClass_GhostVariant` updated to assert standalone ghost format.
- TDD RED gate: tests failed before variants.go change (3 failures). GREEN: all 18 tests pass after.

**Task 2 — CSS replacement + card.templ migration + template hardcode updates (TDD):**
- **button.css**: Replaced 180-line compound-class version with go-backend multi-class selector version (`.ui-button-solid.ui-button-default { ... }`). Added ghost variant rules (`.ui-button-ghost`). Added `.ui-button-soft.ui-button-neutral` (required for existing templates; not in go-backend). Auth-provider CSS correctly excluded (already in `auth.css` from Plan 01).
- **badge.css**: Replaced with go-backend pill-shape version (`border-radius: 999px`). Added `.ui-badge-primary` variant with brand-purple surface.
- **card.css**: Replaced with go-backend token-based version with `.ui-card-header`, `.ui-card-body`, `.ui-card-footer` padding rules.
- **card.templ**: Migrated from `Card(attrs templ.Attributes)` with children to `Card(CardProps)` with typed `Header/Body/Footer templ.Component` fields. Nil-guard conditionals for each region.
- **ui_test.go**: Removed `TestCard_RendersChildren` (old children API). Added `textComponent()` helper, `TestCard_RendersTypedRegions`, `TestBadge_PrimaryVariant`. Added `"io"` import.
- **Template hardcodes**: Updated all compound button class strings across 6 template files.
- **auth_login.templ / auth_signup.templ**: Migrated `@ui.Card(nil) { ... }` to `@ui.Card(ui.CardProps{Body: loginCardBody(...)})` with extracted private templ components.
- **tablos.templ**: `TabloCard` migrated from `@ui.Card(templ.Attributes{id:...}) { ... }` to `<div id={...}>@ui.Card(ui.CardProps{Body: tabloCardBody(...)})` with extracted `tabloCardBody` component.

## Verification Results

- `just generate`: succeeds (no templ compile errors)
- `go test ./... -count=1`: all packages pass (auth, db, files, jobs, web, web/ui, templates)
- `grep ".ui-button-solid.ui-button-default" backend/internal/web/ui/button.css`: matches
- `grep "ui-badge-primary" backend/internal/web/ui/badge.css`: matches
- `grep "type CardProps struct" backend/internal/web/ui/card.templ`: matches
- `grep -r "ui-button-soft-neutral-md|ui-button-solid-default-md|ui-button-soft-danger-md" backend/templates/`: no matches

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Missing critical functionality] Added .ui-button-soft.ui-button-neutral to button.css**
- **Found during:** Task 2, Step 2
- **Issue:** The original button.css had a `.ui-button-soft-neutral-md` compound rule. After migrating to multi-class, `.ui-button-soft.ui-button-neutral` must exist in button.css for the neutral soft buttons in planning.templ, events.templ, and etapes.templ to render correctly. The go-backend button.css only has `.ui-button-solid.ui-button-neutral`, not `.ui-button-soft.ui-button-neutral`.
- **Fix:** Added `.ui-button-soft.ui-button-neutral` with hover/active rules to button.css using the same token variable pattern as other soft variants.
- **Files modified:** `backend/internal/web/ui/button.css`
- **Commit:** a30a6f9

**2. [Rule 3 - Blocking issue] Migrated auth_login.templ, auth_signup.templ, tablos.templ Card call sites**
- **Found during:** Task 2, Step 5
- **Issue:** The plan stated "No production templates call Card with children" but 3 templates used the old `Card(attrs)` API: auth_login.templ, auth_signup.templ, and tablos.templ. These were breaking build failures.
- **Fix:** Extracted content into private templ components (`loginCardBody`, `signupCardBody`, `tabloCardBody`) and passed them as `CardProps.Body`. For TabloCard, the `id` attribute moved to a wrapping `<div>` since `CardProps` has no Attrs field. HTMX targeting via `tablo-delete-zone` class is unaffected.
- **Files modified:** `backend/templates/auth_login.templ`, `backend/templates/auth_signup.templ`, `backend/templates/tablos.templ`
- **Commit:** a30a6f9

## Known Stubs

None — all CSS changes are complete multi-class implementations. All template call sites fully migrated.

## Threat Flags

No new network endpoints, auth paths, file access patterns, or schema changes introduced.

## TDD Gate Compliance

- RED gate Task 1: 3 test failures (TestButton_DefaultSolidMD, TestButtonClass_String, TestButtonClass_GhostVariant) before variants.go update
- GREEN gate Task 1: commit 66f23bb — all 18 tests pass
- RED gate Task 2: compile failure (`undefined: CardProps`) + badge test fail before card.templ migration
- GREEN gate Task 2: commit a30a6f9 — full test suite (all packages) green

## Self-Check: PASSED
