Files
member-console/docs/design-system.md
T
cgalo5758 cbd08b45aa Add project front-door documentation
Rewrite the README around purpose, architecture, quickstart, and
status. Add contributing/security policies plus a docs index with
audience front matter across existing docs.
2026-07-01 04:18:08 -05:00

202 lines
7.7 KiB
Markdown

---
title: "Design System"
audience: [developer]
summary: "Bootstrap-first UI conventions, the custom HTMX primitives, and the WCAG 2.1 AA baseline for building the UI."
---
# Design System
This is a **Bootstrap usage guide plus a custom-primitives reference** — not a
standalone design system. The member-console UI is Bootstrap-first: it uses
[Bootstrap 5.3.6](https://getbootstrap.com/) components and utilities directly,
and supplements them only where Bootstrap genuinely falls short.
Status: M7c deliverable. Reused downstream by M8/M9 member-side UX.
---
## 1. Foundation
### Bootstrap-first
There is **no custom token layer and no parallel utility system**. Spacing,
typography, colors, breakpoints, and layout all come from Bootstrap. Reach for a
Bootstrap utility class or component before writing CSS.
### Role of `app.css`
`internal/embeds/static/app.css` is **supplemental only**. A rule belongs there
only when Bootstrap genuinely cannot express it. Today that is:
- the HTMX loading-indicator hook (`.htmx-indicator` / `.htmx-request`)
- fixed `.toast-container` positioning
- cross-browser `<meter>` styling (Bootstrap does not style `<meter>`)
- the operator nav-tab active-state weight
The file carries a contract comment block at the top stating this. Keep it
short; if it starts to grow a token system, that is a signal to stop.
### Bootstrap CSS variable convention
Do **not** hardcode color values in `app.css` or templates. Reference Bootstrap's
CSS variables so the UI stays in sync if Bootstrap's defaults change:
```css
/* no */ background: #0d6efd;
/* yes */ background: var(--bs-primary);
```
Common ones: `--bs-primary`, `--bs-secondary`, `--bs-body-color`,
`--bs-border-color`, `--bs-success`, `--bs-danger`.
### Future theming hook
Bootstrap 5's CSS-variable architecture is the intended seam for per-deployment
theming (see the "Custom theming (two-tier)" backlog item in
`status/milestones.md`). Tier 1 = operator overrides a handful of `--bs-*`
variables; Tier 2 = unrestricted custom CSS. Nothing is built yet — but because
`app.css` references `--bs-*` instead of hex values, that feature stays a small
server-side addition rather than a refactor.
---
## 2. Approved component palette
### Buttons
| Variant | Use for | Notes |
|---|---|---|
| `btn-primary` | The single primary action on a screen/form | |
| `btn-outline-primary` | Emphasis secondary actions | |
| `btn-outline-secondary` | Neutral secondary actions (navigate, view, sub-views) | Default for "show me more" buttons |
| `btn-danger` / `btn-outline-danger` | Destructive actions | Pair with the confirm modal (§3) |
| `btn-link` | Tertiary / inline actions | |
**Retired — do not use:**
| Variant | Color | Contrast on white | Why |
|---|---|---|---|
| `btn-outline-info` | `#0dcaf0` cyan | ~1.8:1 | Fails WCAG 2.1 AA (needs 4.5:1) |
| `btn-outline-warning` | `#ffc107` yellow | ~1.28:1 | Fails WCAG 2.1 AA (needs 4.5:1) |
Both outline variants render unreadable text against the app's white
background. Replace neutral uses with `btn-outline-secondary` and emphasis uses
with `btn-outline-primary`. (The *solid* `btn-info` / `btn-warning` and the
`text-bg-*` badge variants are unaffected — only the outline button variants are
retired.)
### Badges
Use `text-bg-*` badge variants (`text-bg-secondary`, `text-bg-success`, etc.)
rather than bare `bg-*` where contrast matters — `text-bg-*` pairs the
background with a contrast-checked foreground.
### Alerts
`alert-success` / `alert-danger` / `alert-warning` are fine for **in-page,
persistent** messaging (e.g. validation summaries rendered into a swap target).
For **transient** success/error feedback that must survive an HTMX swap or
navigation, use the toast primitives (§3) instead.
---
## 3. Custom primitives
### Confirm modal
A single shared, accessible confirmation dialog replaces htmx's native
`hx-confirm`. Implemented by `static/confirm-action-modal.js`; the modal markup
lives in the operator layout (`#confirmActionModal`).
Trigger contract — `data-*` attributes on any trigger element:
| Attribute | Required | Meaning |
|---|---|---|
| `data-bs-toggle="modal" data-bs-target="#confirmActionModal"` | yes | Opens the modal |
| `data-action-url` | yes | Endpoint to call |
| `data-action-method` | yes | `post` or `delete` |
| `data-action-target` | yes | CSS selector for the HTMX swap target |
| `data-action-title` | no | Modal title (default `Confirm`) |
| `data-action-body` | no | Modal body text/HTML |
| `data-action-confirm-label` | no | Submit button label (default `Confirm`) |
| `data-action-style` | no | `danger` \| `primary` \| `warning` (default `danger`) |
| `data-action-fields` | no | JSON object of hidden form fields |
Behavior: a successful (2xx/3xx) response closes the modal; a failed request
leaves it open so the operator can read the error in the swap target and retry.
### Error toast
Transient error feedback. Markup (`#errorToast`) lives in each layout's
`.toast-container`; behavior in `static/error-handler.js`. It is shown
automatically by the `htmx:beforeSwap` handler for 4xx/5xx/network failures, and
can be shown manually via `showErrorToast(message)`.
- `role="alert"`, `aria-live="assertive"` — interrupts the screen reader, because
an error needs immediate attention.
### Success toast
Transient success feedback. Markup (`#successToast`) lives in each layout's
`.toast-container`; behavior in `static/success-toast.js`.
Triggered from the server via an `HX-Trigger` response header:
```go
// Works for both HTMX partial swaps and hx-boost navigations.
w.Header().Set("HX-Trigger", `{"showSuccessToast": "Product created"}`)
```
- `role="status"`, `aria-live="polite"` — announced without interrupting,
because success is not urgent.
- The `.toast-container` lives outside any HTMX swap target, so the toast
survives partial swaps and `hx-boost` full-page navigations.
An inline `alert-success` in a POST response body would be discarded by the
redirect target and never seen — that is why this primitive exists.
Existing templates are **not** migrated to this primitive in M7c; M7e owns the
convention and timing for adopting it across flows.
### Loading states
HTMX request indicators use the `.htmx-indicator` hook (styled in `app.css`).
Place a `.htmx-indicator` element inside the triggering control and let HTMX
toggle it via `.htmx-request`. For destructive forms, also use
`hx-disabled-elt` to disable the submit button during the request (see the
confirm modal form).
### Empty states
Empty collections render a centered, muted message rather than a bare blank
area:
```html
<div class="text-center py-4">
<p class="text-muted mb-0">No plan ladders found.</p>
</div>
```
Keep the copy specific ("No plan ladders found") rather than generic ("No
data").
---
## 4. WCAG 2.1 AA baseline
- **Contrast.** Text and meaningful UI must meet 4.5:1 (3:1 for large text /
components). The two retired outline button variants (§2) failed this; check
any new color use against it.
- **`aria-live` for async regions.** Content that appears without a navigation —
toasts, and HTMX swap targets that surface validation or results — must be in
an `aria-live` region so screen readers announce the change. Use `assertive`
only for errors; `polite` for everything else.
- **Focus management.** Modals trap and restore focus (Bootstrap handles this for
`.modal`). Do not remove focus outlines. Interactive controls must be
reachable and operable by keyboard.
- **Semantic markup.** Use real `<button>`/`<a>` elements (not clickable
`<div>`s), associate every form control with a `<label>`, and give icon-only
controls an `aria-label`.
- **Audit.** M7f runs the formal axe + Lighthouse a11y pass and keyboard-nav
review across all operator screens; this section is the standing checklist
that pass measures against.