|
| 1 | +# cf-ui Django Migration Guide |
| 2 | + |
| 3 | +You are a migration agent. Your job is to migrate a Django project to use `cf-ui` components. Follow the phases below in order. Do not skip Phase 1 — the project context it surfaces determines everything that follows. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Phase 1: Understand the project |
| 8 | + |
| 9 | +Before writing a single line of code, read the project's own documentation and explore its templates. |
| 10 | + |
| 11 | +**Step 1 — Read `CLAUDE.md`** (or `README.md` if no CLAUDE.md exists). Extract: |
| 12 | +- Framework versions (Django, django-cotton, Alpine.js if any) |
| 13 | +- CSS setup — is Bulma loaded from CDN or compiled from source SCSS? |
| 14 | +- Any existing component patterns mentioned |
| 15 | +- Test commands |
| 16 | + |
| 17 | +**Step 2 — Explore existing templates.** Find all template directories and read a representative sample: |
| 18 | +- The base layout template (look for `base.html`, `layouts/base.html`, `cotton/layouts/base.html`) |
| 19 | +- Any existing cotton component templates (directories named `cotton/`) |
| 20 | +- How CSS and JS assets are currently loaded (look for `<link>` and `<script>` tags or asset snippets) |
| 21 | +- Forms — how are they rendered? (crispy forms, manual fields, cotton components?) |
| 22 | +- Any existing notification, modal, card, table, or navbar patterns |
| 23 | + |
| 24 | +**Step 3 — Identify Alpine.js status.** Is Alpine already loaded? Is there vanilla JS doing what Alpine would handle (e.g., navbar burger toggles, modal open/close)? |
| 25 | + |
| 26 | +**Step 4 — Check `component-framework` version.** cf-ui depends on `component-framework>=0.4`. Run: |
| 27 | +```bash |
| 28 | +pip show component-framework |
| 29 | +``` |
| 30 | +If not installed or below 0.4, install from GitHub: |
| 31 | +```bash |
| 32 | +pip install "git+https://github.com/fsecada01/component-framework.git" |
| 33 | +``` |
| 34 | + |
| 35 | +After Phase 1, write a short summary of what you found before proceeding. |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Phase 2: Assess migration candidates |
| 40 | + |
| 41 | +Based on what you discovered, categorise the existing templates: |
| 42 | + |
| 43 | +**Good candidates for cf-ui** — generic Bulma UI patterns that cf-ui already covers: |
| 44 | +- Inline notification divs (`<div class="notification is-...">`) |
| 45 | +- Django messages framework snippets |
| 46 | +- Modals (especially any using legacy Semantic UI or bare Bulma `<div class="modal">`) |
| 47 | +- Progress bars (`<progress class="progress ...">`) |
| 48 | +- Paginated list views |
| 49 | +- Tables with sorting or pagination |
| 50 | +- Panels / collapsible sections |
| 51 | +- Navbar burger toggle (if using vanilla JS) |
| 52 | + |
| 53 | +**Leave alone** — project-specific components with custom styling or logic: |
| 54 | +- Components using custom CSS class naming (e.g., BEM `.project-*` classes) |
| 55 | +- Components driven by a design system with its own tokens and overrides |
| 56 | +- Forms rendered by crispy-forms or a form library (replacing these requires reworking field rendering, not just markup) |
| 57 | +- Any component that is already a well-maintained cotton component doing something cf-ui doesn't cover |
| 58 | + |
| 59 | +Write a prioritised list of candidates before proceeding. Confirm with the user if anything is unclear. |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +## Phase 3: Apply settings changes |
| 64 | + |
| 65 | +These steps apply to every Django project. Confirm each before moving to migration. |
| 66 | + |
| 67 | +**1. Install cf-ui:** |
| 68 | +```bash |
| 69 | +pip install "cf-ui[bulma]" |
| 70 | +# Add to requirements.txt / pyproject.toml as appropriate |
| 71 | +``` |
| 72 | + |
| 73 | +**2. Add to `INSTALLED_APPS`:** |
| 74 | +```python |
| 75 | +"cf_ui.django.CfUiConfig", # use the full class path, not just "cf_ui.django" |
| 76 | +``` |
| 77 | + |
| 78 | +**3. Set theme:** |
| 79 | +```python |
| 80 | +CF_UI_THEME = "bulma" |
| 81 | +``` |
| 82 | + |
| 83 | +**4. Register the template tag library** (required — autodiscovery doesn't work because the app name is `cf_ui.django`): |
| 84 | +```python |
| 85 | +TEMPLATES = [{ |
| 86 | + ... |
| 87 | + "OPTIONS": { |
| 88 | + "libraries": {"cf_ui": "cf_ui.templatetags.cf_ui"}, |
| 89 | + }, |
| 90 | +}] |
| 91 | +``` |
| 92 | + |
| 93 | +**5. Asset loading — read the project's CSS setup first:** |
| 94 | + |
| 95 | +- **If Bulma is loaded from CDN** (a bare `<link href="...bulma...cdn...">` in the base template): replace it with `{% cf_ui_head %}`, which outputs the Bulma CDN link plus the `[x-cloak]` style. |
| 96 | +- **If Bulma is compiled from source SCSS** with custom variables or brand overrides: do **not** call `{% cf_ui_head %}` — it would load a second, conflicting stylesheet. Only call `{% cf_ui_body %}` for Alpine. |
| 97 | +- **In both cases**, add `{% cf_ui_body %}` before `</body>` in the base template to load `cf_ui_alpine.js` + Alpine CDN. |
| 98 | + |
| 99 | +```django |
| 100 | +{# In the base template #} |
| 101 | +{% load cf_ui %} |
| 102 | +... |
| 103 | +{% cf_ui_body %} {# loads cf_ui_alpine.js + Alpine CDN #} |
| 104 | +</body> |
| 105 | +``` |
| 106 | + |
| 107 | +**6. Verify Alpine is loading.** Open the app in a browser and confirm `window.Alpine` is defined in the console before proceeding with interactive components. |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## Phase 4: Migrate components |
| 112 | + |
| 113 | +Work through your prioritised list one component at a time. For each: |
| 114 | + |
| 115 | +1. Read the existing template in full |
| 116 | +2. Identify the equivalent cf-ui component from the reference below |
| 117 | +3. Write the replacement, preserving all data bindings, HTMX attributes, and CSS modifiers |
| 118 | +4. Run the test suite |
| 119 | +5. Visually verify in a browser |
| 120 | +6. Commit |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## cf-ui component reference |
| 125 | + |
| 126 | +### Critical gotchas |
| 127 | + |
| 128 | +| Gotcha | Detail | |
| 129 | +|---|---| |
| 130 | +| `extra_class` not `class` | All cf-ui components use `extra_class` for the root element — `class` is reserved in JinjaX `{#def}` headers | |
| 131 | +| `input_class` for inner elements | FormField, Select, Textarea accept `input_class` for the `<input>`/`<select>`/`<textarea>` element; CheckboxGroup accepts `control_class` for the `<div class="control">` | |
| 132 | +| `<c-vars>` not `<c-props>` | django-cotton 2.x renamed the declaration tag — cf-ui uses `<c-vars>` throughout | |
| 133 | +| Alpine required for interactive components | Modal, Notification (dismissible), Panel, Navbar burger, Tabs all need Alpine. Confirm `{% cf_ui_body %}` is in the base template before migrating these | |
| 134 | +| Modal control via Alpine store | Trigger a modal from outside: `Alpine.store('cf').modal.open('modal-id')`. Works only after Alpine has initialised | |
| 135 | + |
| 136 | +### Notification |
| 137 | + |
| 138 | +```html |
| 139 | +{# Before — common patterns #} |
| 140 | +<div class="notification is-danger is-light">{{ error }}</div> |
| 141 | +<div class="notification is-info">{{ message }}</div> |
| 142 | + |
| 143 | +{# After #} |
| 144 | +<c-cf.notification message="{{ error }}" type="danger" dismissible="false" /> |
| 145 | +<c-cf.notification message="{{ message }}" type="info" dismissible="true" /> |
| 146 | +``` |
| 147 | + |
| 148 | +Supported types: `info`, `success`, `warning`, `danger`. `dismissible="true"` adds an Alpine-driven close button. |
| 149 | + |
| 150 | +### Modal |
| 151 | + |
| 152 | +```html |
| 153 | +{# After #} |
| 154 | +<c-cf.modal id="my-modal"> |
| 155 | + <c-slot name="header">Dialog Title</c-slot> |
| 156 | + Modal body content here. |
| 157 | + <c-slot name="footer"> |
| 158 | + <button class="button" @click="close()">Cancel</button> |
| 159 | + <button class="button is-primary" @click="close()">Confirm</button> |
| 160 | + </c-slot> |
| 161 | +</c-cf.modal> |
| 162 | + |
| 163 | +{# Trigger from anywhere once Alpine is loaded #} |
| 164 | +<button @click="Alpine.store('cf').modal.open('my-modal')">Open</button> |
| 165 | +``` |
| 166 | + |
| 167 | +### Progress |
| 168 | + |
| 169 | +```html |
| 170 | +{# Before #} |
| 171 | +<progress class="progress is-primary" value="60" max="100">60%</progress> |
| 172 | + |
| 173 | +{# After #} |
| 174 | +<c-cf.progress value="60" max="100" type="primary" /> |
| 175 | +<c-cf.progress value="{{ pct }}" max="1" type="{{ bar_type }}" label="Resume Fit" extra_class="is-small" /> |
| 176 | +``` |
| 177 | + |
| 178 | +### Card |
| 179 | + |
| 180 | +```html |
| 181 | +{# After #} |
| 182 | +<c-cf.card> |
| 183 | + <c-slot name="header">Card Title</c-slot> |
| 184 | + Card body content. |
| 185 | + <c-slot name="footer"> |
| 186 | + <a class="button is-primary is-small">Action</a> |
| 187 | + </c-slot> |
| 188 | +</c-cf.card> |
| 189 | +``` |
| 190 | + |
| 191 | +### Table |
| 192 | + |
| 193 | +```html |
| 194 | +{# After — columns is a list of {key, label} dicts; rows is a list of dicts #} |
| 195 | +<c-cf.table columns="{{ columns }}" rows="{{ rows }}" |
| 196 | + hx_url="{% url 'my-list-view' %}" hx_target="#list-container" /> |
| 197 | +``` |
| 198 | + |
| 199 | +### Pagination |
| 200 | + |
| 201 | +```html |
| 202 | +{# After #} |
| 203 | +<c-cf.pagination page="{{ page_obj.number }}" |
| 204 | + total_pages="{{ page_obj.paginator.num_pages }}" |
| 205 | + hx_url="{% url 'my-list-view' %}" |
| 206 | + hx_target="#list-container" /> |
| 207 | +``` |
| 208 | + |
| 209 | +`hx_url` and `hx_target` are optional. Without them, links render without `hx-get` attributes (plain `<a>` tags with no HTMX). |
| 210 | + |
| 211 | +### Panel (collapsible) |
| 212 | + |
| 213 | +```html |
| 214 | +{# After #} |
| 215 | +<c-cf.panel title="Advanced Filters" open="false"> |
| 216 | + Filter content here. |
| 217 | +</c-cf.panel> |
| 218 | +``` |
| 219 | + |
| 220 | +### Form fields |
| 221 | + |
| 222 | +Use only when rendering fields manually (not via crispy-forms or a form library). If the project uses crispy-forms, leave it — replacing crispy with cf-ui form fields requires reworking every form's rendering logic, which is outside the scope of a cf-ui migration. |
| 223 | + |
| 224 | +```html |
| 225 | +{# After — manual field rendering #} |
| 226 | +<c-cf.form-field name="email" label="Email" |
| 227 | + value="{{ form.email.value|default:'' }}" |
| 228 | + error="{{ form.email.errors.0|default:'' }}" |
| 229 | + type="email" /> |
| 230 | +``` |
| 231 | + |
| 232 | +### Navbar burger |
| 233 | + |
| 234 | +Migrate only if the project uses vanilla JS to toggle the burger. If already using Alpine or another reactive approach, leave it. |
| 235 | + |
| 236 | +```html |
| 237 | +{# After — add x-data="cfNavbar" to the <nav> element #} |
| 238 | +<nav class="navbar" x-data="cfNavbar"> |
| 239 | + <div class="navbar-brand"> |
| 240 | + ... |
| 241 | + <a class="navbar-burger" @click="toggle()" :class="{ 'is-active': menuOpen }"> |
| 242 | + <span aria-hidden="true"></span> |
| 243 | + <span aria-hidden="true"></span> |
| 244 | + <span aria-hidden="true"></span> |
| 245 | + </a> |
| 246 | + </div> |
| 247 | + <div class="navbar-menu" :class="{ 'is-active': menuOpen }"> |
| 248 | + ... |
| 249 | + </div> |
| 250 | +</nav> |
| 251 | +``` |
| 252 | + |
| 253 | +Remove any vanilla JS that was doing the same toggle. |
| 254 | + |
| 255 | +--- |
| 256 | + |
| 257 | +## Verification checklist |
| 258 | + |
| 259 | +Run after completing all migrations: |
| 260 | + |
| 261 | +- [ ] Django test suite passes |
| 262 | +- [ ] No JavaScript console errors on page load |
| 263 | +- [ ] `window.Alpine` is defined in browser console |
| 264 | +- [ ] Notifications render correctly and dismiss (if dismissible) |
| 265 | +- [ ] Modal opens via `Alpine.store('cf').modal.open(id)` and closes via the delete button |
| 266 | +- [ ] Pagination links fire correctly (HTMX swap or full page reload as intended) |
| 267 | +- [ ] Navbar burger toggles the menu on mobile viewport |
| 268 | +- [ ] No duplicate Bulma stylesheets loaded (check Network tab) |
0 commit comments