# Form Overhaul Plan > Last updated: 2026-05-12 > Status: Decided — awaiting implementation > > **Decisions made:** > - All forms (simple and complex) get section headers for consistency > - Two-column layout uses **flexbox** (auto-reflow on different screen sizes) > - `cotton/layouts/add.html` enhanced with **Option A**: `c-section` component slots > - `add_purchase.html` dual-submit **simplified** — remove ``, use same `c-button` pattern as `add_game.html` > - GameStatusChange delete confirmation **converted to modal** (via HTMX trigger) ## Goal Modernize all forms and form-like elements to align with Flowbite design, improve visual consistency, and adopt responsive multi-column layouts for complex forms. --- ## Current State Analysis ### Form Pages (add/edit) All use `cotton/layouts/add.html` — single column, `max-w-xl`, `form.as_div`: | Page | Form | Fields | Complexity | |---|---|---|---| | Game | `GameForm` | 7 fields: name, sort_name, platform, year, year_orig, status, mastered, wikidata | Medium | | Purchase | `PurchaseForm` | 11 fields: games, platform, dates, price, currency, type, ownership, related, infinite, name | High | | Session | `SessionForm` | 8 fields: game, timestamps, duration, emulated, device, note, checkbox (custom rendering) | High | | Platform | `PlatformForm` | 3 fields: name, icon, group | Low | | Device | `DeviceForm` | 2 fields: name, type | Low | | PlayEvent | `PlayEventForm` | 5 fields: game, dates, note, checkbox | Low | | GameStatusChange | `GameStatusChangeForm` | 4 fields | Low | ### Other Form-Like Elements | Element | Template | Notes | |---|---|---| | Login | `registration/login.html` | Flowbite card, already good | | Search | `cotton/search_field.html` | Reusable, already good | | Delete Game | `partials/delete_game_confirmation.html` | Inline modal, inconsistent button layout | | Delete PlayEvent | `gamestatuschange_confirm_delete.html` | Full-page form, no modal | | Refund Purchase | `partials/refund_purchase_confirmation.html` | Inline modal, inconsistent button layout | | Stats Year Select | `stats.html` | Manual ``** has no Flowbite styling 9. **Search field** is not wrapped in `
` — no native clear-on-Enter behavior ### P2: Styling Consistency 10. **Status/device selectors** use old Tailwind v3 patterns (`rounded-sm`, `shadow-2xs`, `border-gray-200` without explicit color) 11. **`navbar.html` buttons** use `rounded-sm` instead of `rounded-base` 12. **`simple_table.html` pagination buttons** use `rounded-s-lg`/`rounded-e-lg` — could be simplified --- ## Proposed Improvements ### 1. Two-Column Layout for Complex Forms (Flexbox) **Scope**: `GameForm`, `PurchaseForm`, `PlayEventForm`, `SessionForm` Use **flexbox** with wrap behavior so fields auto-reflow on different screen sizes. No fixed column count — fields sit side-by-side on `md:`+ and wrap naturally on smaller screens. #### GameForm Layout ``` ┌──────────────────────────────────┐ │ Game Details │ │ ┌──────────────────┬───────────┐ │ │ │ Name │ Platform │ │ │ │ Sort Name │ Year │ │ │ │ Original Year │ Wikidata │ │ │ └──────────────────┴───────────┘ │ │ Status │ │ ┌──────────────────┬───────────┐ │ │ │ Status │ Mastered │ │ │ └──────────────────┴───────────┘ │ │ [Submit] │ └──────────────────────────────────┘ ``` #### PurchaseForm Layout (simplified) ``` ┌──────────────────────────────────────────┐ │ Purchase Details │ │ ┌──────────────────────┬───────────────┐ │ │ │ Games (multi-select) │ Platform │ │ │ │ Type │ Ownership │ │ │ │ Name │ Related Purch │ │ │ └──────────────────────┴───────────────┘ │ │ Dates │ Price │ │ ┌───────────────┬──────┴───────────────┐ │ │ │ Date Purch │ Price Curr │ │ │ │ Date Refund │ Infinite [ ] │ │ │ └───────────────┴──────────────────────┘ │ │ [Submit] [Submit + Session] │ └──────────────────────────────────────────┘ ``` **Implementation**: `c-section` component accepts `columns="2"` (or `"3"`) which applies `flex flex-wrap gap-4 [&>div]:w-[calc(50%-0.5rem)]` on md+ screens. Each field wraps in a `
` inside the section slot. **Decision**: Dual-submit in `add_purchase.html` simplified — remove ``, use same `` pattern as `add_game.html`. ### 2. Field Grouping with Card Sections **Decision**: ALL forms get section headers for consistency (not just complex forms). Group related fields with section headings and subtle borders/backgrounds: ```html {{ form.name }} {{ form.platform }} {{ form.sort_name }} {{ form.year_released }} ``` Each section renders as: ```html

Section Title

``` Each section gets: - Subtle background (`bg-neutral-primary-soft/30`) - Top border with spacing (`border-t border-default-medium`) - Section heading (`text-sm font-medium text-heading uppercase mb-4`) - Flexbox gap for responsive field reflow ### 1b. `c-section` Component Specification New cotton component for the `cotton/` directory: ```python # games/templates/cotton/section.py (or inline in components.py) from common.components import Div def Section(title: str = "", columns: str = "1", children: str = "") -> SafeText: """Renders a form field section with optional multi-column flexbox layout. Args: title: Section heading (renders as uppercase label) columns: "1" (default), "2", or "3" — target column count on md+ screens children: Field markup (each field wrapped in
for flex wrapping) """ col_class = { "1": "flex flex-col", "2": "flex flex-wrap gap-4 [&>div]:w-[calc(50%-0.5rem)]", "3": "flex flex-wrap gap-4 [&>div]:w-[calc(33.333%-0.67rem)]", }.get(columns, "flex flex-col") return Div( cls=f"form-section p-5 border-t border-default-medium bg-neutral-primary-soft/30 first-of-type:border-t-0 first-of-type:pt-0", children=f"""

{title}

{children}
""" ) ``` **Template usage:** ```django {# add_game.html #}
{{ form.name }}
{{ form.platform }}
{{ form.sort_name }}
{{ form.year_released }}
{{ form.original_year_released }}
{{ form.wikidata }}
{{ form.status }}
{{ form.mastered }}
``` **`cotton/layouts/add.html` changes:** - Remove hardcoded `{{ form.as_div }}` rendering - Accept optional `sections` variable (list of rendered `c-section` output) - If `sections` provided, render them; otherwise fall back to `{{ form.as_div }}` for simple forms - Keep `additional_row` slot for dual-submit buttons ### 3. CSS/Style Fixes #### `input.css` changes: ```css /* Update errorlist */ .errorlist { @apply mt-4 mb-1 pl-3 py-2 bg-red-600 text-slate-200 w-full max-w-xl; /* was w-[300px] */ } /* Remove: #button-container, .basic-button — unused legacy */ /* Remove: .flowbite-input — custom class is code smell with Tailwind */ /* Remove: flowbite-input @apply block (line 229-234) */ /* Add Flowbite styling for select in stats */ #yearSelect { @apply bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand; } ``` **Important**: The styling previously provided by `.flowbite-input` must be preserved. The element-level `@apply` rules for `input`, `select`, and `textarea` in `input.css` (lines 209-219) already provide equivalent styling. These rules automatically apply to all form inputs without needing custom classes: - `input:not([type="checkbox"])` — background, border, text, radius, focus ring, padding - `select` — same base styling as inputs - `textarea` — same base styling with adjusted padding **Files to clean up:** - `common/input.css`: Remove `.flowbite-input` class entirely (lines 229-234) - `games/forms.py`: Remove `flowbite_input_widget` and `flowbite_password_widget` (lines 22-23) - `games/forms.py`: Remove `widget=` from `LoginForm` fields (lines 28, 32) — login template uses explicit Tailwind classes already #### Rewrite `modal.html`: - Remove stray `` tag and restructure as a proper cotton component - New `c-modal` component with: `modal_id`, `title`, `size="xl"`, `backdrop_close` variables - `{{ slot }}` (cotton default slot) for body content — passed as children of ``, no block tags needed - `{{ footer }}` (optional named slot via ``) for non-form buttons - Reusable `cotton/close_button.html` via `` - Size mapping via inline `{% if %}`: `{% if size == 'sm' %}max-w-sm{% elif size == 'lg' %}max-w-lg{% else %}max-w-xl{% endif %}` - Horizontal centering: `mx-auto` on inner container (matching old modal pattern) - Click-to-dismiss backdrop with `event.stopPropagation()` on inner container - Flowbite-style styling: `rounded-lg shadow`, `bg-white dark:bg-gray-800`, `sm:p-5` ### 4. Unify Delete Confirmations (All Modal) **Decision**: GameStatusChange delete confirmation converted from full-page to modal. All three use the same modal pattern. **Target**: All confirmation modals use the same pattern: ```html

Confirm Action

Are you sure...?

{% if details %}
  • {{ detail }}
{% endif %}

This action cannot be undone.

Delete Cancel
``` - **Delete Game** (`partials/delete_game_confirmation.html`): Update template to match standard pattern - **Delete StatusChange** (`gamestatuschange_confirm_delete.html` → `partials/statuschange_delete_confirmation.html`): Adopt the same 2-view pattern as delete-game. - Add `delete_statuschange_confirmation` view (GET → renders modal partial) + URL before the delete URL - Update `partials/history.html` — add `hx-get="{% url 'games:delete_statuschange_confirmation' change.id %}" hx-target="#global-modal-container"` to the Delete link - Create new `partials/statuschange_delete_confirmation.html` using ``, same structure as `delete_game_confirmation.html` (detail list, red warning text, same button layout, `` badge for old status) - Modify `GameStatusChangeDeleteView` to only handle POST (remove its GET-rendered template) - Delete old `gamestatuschange_confirm_delete.html` after migration - **Refund Purchase** (`partials/refund_purchase_confirmation.html`): Update template to match standard pattern ### 5. Search Form Enhancement Wrap `search_field.html` in proper `
`: ```html
``` This enables: - Native form submission on Enter - Potential for "clear all" functionality - Proper browser form autofill behavior ### 6. Status/Device Selector Styling Update Alpine.js dropdowns to use consistent button classes: - Replace `rounded-lg` with `rounded-base` - Replace `shadow-2xs` with `shadow-xs` - Standardize border colors with `border-default` - Use `text-heading` / `text-body` for dark mode compatibility --- ## Templates That Need Changes | Template | Change | Effort | |---|---|---| | `cotton/layouts/add.html` | Add `c-section` component support (title, columns, fields slots) | Medium | | `add_game.html` | Multi-column flexbox layout, section headers | Medium | | `add_purchase.html` | Multi-column flexbox layout, simplify dual-submit, section headers | High | | `add_session.html` | Flexbox layout for timestamps+duration, section headers | Low | | `add_playevent.html` | Flexbox layout, section headers | Low | | `add_platform.html` | Section headers (was flat single-column) | Low | | `add_device.html` | Section headers (was flat single-column) | Low | | `partials/delete_game_confirmation.html` | Standardize to shared modal pattern | Low | | `partials/refund_purchase_confirmation.html` | Standardize to shared modal pattern | Low | | `partials/statuschange_delete_confirmation.html` | New — adopt same 2-view pattern as delete-game (modal, ``, HTMX triggers) | Medium | | `gamestatuschange_confirm_delete.html` | Delete (replaced by new partial) | Trivial | | `cotton/modal.html` | Fix missing `
` tag | Low | | `stats.html` | Add Flowbite select styling | Low | | `partials/gamestatus_selector.html` | Update button classes | Low | | `partials/sessiondevice_selector.html` | Update button classes | Low | | `cotton/search_field.html` | Wrap in `` | Low | | `common/input.css` | Remove legacy, fix errorlist, add select styles | Low | --- ## Implementation Order ### Phase 1: Quick Wins (low risk, no breaking changes) 1. **CSS fixes** (`input.css`) — fix errorlist width, remove legacy `.basic-button` / `#button-container`, add select styles 2. ~~**`modal.html` rewrite**~~ — add missing `` tag, conditional form wrapper ✓ Implemented (uses `{{ slot }}` cotton default slot, no `{% partial %}` tags; `size` defaults to `"xl"` with inline `{% if %}` mapping) 3. **Delete confirmation standardization** — 3 templates → all modal, same pattern (including GameStatusChange: full-page → modal) 4. **Search field enhancement** — wrap in `` 5. **Stats select styling** — add Flowbite select classes 6. **Selector styling updates** — gamestatus + sessiondevice selectors, consistent classes ### Phase 2: `c-section` Component 7. **Create `c-section` component** — title, columns, fields slots 8. **Update `cotton/layouts/add.html`** — support `sections` variable, fallback to `form.as_div` ### Phase 3: Form Layout Overhaul (largest change) 9. **`GameForm`** — section headers + 2-col flexbox (`add_game.html`) 10. **`PlayEventForm`** — section headers + 2-col flexbox 11. **`PurchaseForm`** — section headers + 2/3-col flexbox + simplify dual-submit (`add_purchase.html`) 12. **`SessionForm`** — section headers + flexbox for timestamps+duration (custom rendering already exists) 13. **Simple forms** — `add_platform.html`, `add_device.html` get section headers (single column) --- ## Testing Strategy - Run `make test` after Phase 1 changes to verify nothing broke - `tests/test_paths_return_200.py` — URL-level smoke tests (186 tests). All views must have a `test_*_returns_200` test. Adding new views requires a corresponding test to prevent `TemplateDoesNotExist` regressions. - CSS changes do not require test changes (no test coverage for rendering), but visual verification is recommended --- ## Open Questions - [x] Simple forms section headers? → **All forms get section headers** for consistency - [x] CSS Grid or Flexbox? → **Flexbox** — auto-reflow on different screen sizes - [x] add.html layout variable? → **Option A** — `c-section` cotton component with `title` and `columns` slots - [x] add_purchase.html dual-submit? → **Simplify** — remove ``, use same `` pattern as `add_game.html` - [x] GameStatusChange modal or full-page? → **Modal** — trigger via HTMX, same pattern as delete-game - [x] .flowbite-input class? → **Remove entirely** — rely on element-level `@apply` in `input.css` ## Decision Summary | Question | Decision | |---|---| | Section headers on simple forms | Yes, all forms get them | | Layout approach for multi-column | Flexbox with wrap | | Layout mechanism in add.html | Option A: `c-section` cotton component | | Purchase dual-submit | Simplify — single submit button, same as Game | | GameStatusChange delete | Convert to modal (HTMX-triggered) | | .flowbite-input class | Remove — preserve styling via element-level `@apply` in `input.css` | | `modal.html` component | Rewrite with form wrapping, body + footer slots, reusable close button ✓ Implemented ## Build Step After any CSS changes to `common/input.css`, the compiled output must be rebuilt: - **`make css`** — one-shot build: `npx @tailwindcss/cli -i ./common/input.css -o ./games/static/base.css` - **`make dev`** — watch mode: Tailwind rebuilds automatically on every `input.css` save Running `make dev` is sufficient for development since it concurrently runs Django and the CSS watcher. Only use `make css` if you only want to rebuild CSS without starting the dev server. **Important**: Legacy CSS removals (`.basic-button`, `#button-container`, `.flowbite-input`) will only take effect in the browser after a rebuild. The old compiled `base.css` will still contain them until rebuilt.