--- 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 `` styling (Bootstrap does not style ``) - 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

No plan ladders found.

``` 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 `