Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1# TailwindCSS Migration Implementation Plan
2
3> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
4
5**Goal:** Replace the ~1000 lines of custom CSS with TailwindCSS utility classes, while preserving the existing design and dark mode support.
6
7**Architecture:**
81. Install TailwindCSS as a dev dependency with PostCSS
92. Create a minimal `tailwind.config.js` that extends the default theme with the existing color palette
103. Keep a small `base.css` for critical custom styles that can't be replaced (e.g., CodeMirror overrides, Milkdown-specific styles)
114. Replace HTML class names with Tailwind utility classes in templates
125. Use `@apply` for complex patterns that appear multiple times
13
14**Tech Stack:** TailwindCSS 4.x, PostCSS, existing esbuild for CSS bundling
15
16---
17
18## Current CSS Inventory
19
20| File | Lines | Purpose |
21|------|-------|---------|
22| `static/css/style.css` | 427 | Base styles, navbar, cards, buttons, forms, alerts |
23| `static/css/editor.css` | 419 | Editor layout, toolbar, modals, split pane |
24| `static/css/markdown.css` | 84 | Markdown preview rendering |
25| `static/css/diff.css` | 60 | Diff view styling |
26| **Total** | **990** | |
27
28---
29
30## Chunk 1: Setup TailwindCSS
31
32**Files:**
33- Modify: `/Users/johnluther/projects/diffdown/package.json`
34- Create: `/Users/johnluther/projects/diffdown/tailwind.config.js`
35- Create: `/Users/johnluther/projects/diffdown/postcss.config.js`
36- Create: `/Users/johnluther/projects/diffdown/src/styles/input.css`
37
38- [ ] **Step 1: Install TailwindCSS and dependencies**
39
40Run: `npm install -D tailwindcss postcss autoprefixer`
41
42- [ ] **Step 2: Initialize Tailwind config**
43
44```javascript
45/** @type {import('tailwindcss').Config} */
46module.exports = {
47 content: [
48 "./templates/**/*.html",
49 "./static/**/*.js",
50 ],
51 darkMode: 'class', // Use data-theme attribute for dark mode
52 theme: {
53 extend: {
54 colors: {
55 bg: 'var(--bg)',
56 'bg-card': 'var(--bg-card)',
57 text: 'var(--text)',
58 'text-muted': 'var(--text-muted)',
59 border: 'var(--border)',
60 primary: 'var(--primary)',
61 'primary-hover': 'var(--primary-hover)',
62 danger: 'var(--danger)',
63 success: 'var(--success)',
64 'code-bg': 'var(--code-bg)',
65 'alert-error-bg': 'var(--alert-error-bg)',
66 'alert-error-border': 'var(--alert-error-border)',
67 },
68 fontFamily: {
69 sans: ['"Barlow"', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
70 heading: ['"Lexend"', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
71 mono: ['"JetBrains Mono"', '"Fira Code"', '"Cascadia Code"', 'monospace'],
72 },
73 borderRadius: {
74 DEFAULT: '6px',
75 },
76 },
77 },
78 plugins: [],
79}
80```
81
82- [ ] **Step 3: Create PostCSS config**
83
84```javascript
85module.exports = {
86 plugins: {
87 tailwindcss: {},
88 autoprefixer: {},
89 },
90}
91```
92
93- [ ] **Step 4: Create input.css for Tailwind directives**
94
95```css
96@tailwind base;
97@tailwind components;
98@tailwind utilities;
99
100/* Keep custom font import */
101@import url(https://fonts.bunny.net/css?family=barlow:100,200,300,400,500,600|lexend:100,200,300,400,500,600,700,800,900);
102
103/* CSS variables - kept for backward compatibility */
104:root {
105 --bg: #fafafa;
106 --bg-card: #fff;
107 --text: #1a1a2e;
108 --text-muted: #6b7280;
109 --border: #e5e7eb;
110 --primary: #2563eb;
111 --primary-hover: #1d4ed8;
112 --danger: #dc2626;
113 --success: #16a34a;
114 --code-bg: #f3f4f6;
115 --alert-error-bg: #fef2f2;
116 --alert-error-border: #fecaca;
117}
118
119[data-theme="dark"] {
120 --bg: #0d1117;
121 --bg-card: #161b22;
122 --text: #e6edf3;
123 --text-muted: #ededed;
124 --text-secondary: #d4d4d4;
125 --border: #30363d;
126 --code-bg: #1f2428;
127 --primary: #388bfd;
128 --primary-hover: #58a6ff;
129 --danger: #f85149;
130 --success: #3fb950;
131 --alert-error-bg: #1c0608;
132 --alert-error-border: #6e1c20;
133}
134
135@media (prefers-color-scheme: dark) {
136 :root:not([data-theme="light"]) {
137 --bg: #0d1117;
138 --bg-card: #161b22;
139 --text: #e6edf3;
140 --text-muted: #ededed;
141 --text-secondary: #d4d4d4;
142 --border: #30363d;
143 --code-bg: #1f2428;
144 --primary: #388bfd;
145 --primary-hover: #58a6ff;
146 --danger: #f85149;
147 --success: #3fb950;
148 --alert-error-bg: #1c0608;
149 --alert-error-border: #6e1c20;
150 }
151}
152```
153
154- [ ] **Step 5: Add build script to package.json**
155
156```json
157"scripts": {
158 "build:css": "npx tailwindcss -i ./src/styles/input.css -o ./static/css/tailwind.css",
159 "build:collab": "npx esbuild node_modules/prosemirror-collab/dist/index.js --bundle --format=esm --outfile=static/vendor/collab.js",
160 "build": "npm run build:css && npm run build:collab"
161}
162```
163
164- [ ] **Step 6: Build TailwindCSS**
165
166Run: `npm run build:css`
167
168Expected: Creates `static/css/tailwind.css` with Tailwind utilities
169
170- [ ] **Step 7: Commit**
171
172```bash
173git add package.json tailwind.config.js postcss.config.js src/styles/input.css static/css/tailwind.css
174git commit -m "feat: add TailwindCSS setup"
175```
176
177---
178
179## Chunk 2: Replace CSS in base.html
180
181**Files:**
182- Modify: `/Users/johnluther/projects/diffdown/templates/base.html`
183
184- [ ] **Step 1: Replace navbar styles with Tailwind classes**
185
186Old:
187```html
188<nav class="navbar">
189 <a href="/" class="logo">
190 <img src="/static/img/dd-logo.svg" alt="" width="22" height="28" style="vertical-align:middle;margin-right:0.4rem">Diffdown
191 </a>
192 <div class="nav-right">
193 {{if .User}}
194 <a href="/">Documents</a>
195 <a href="/about">About</a>
196 <span class="nav-user">{{.User.Name}}</span>
197 <form method="post" action="/auth/logout" style="display:inline">
198 <button type="submit" class="btn-link">Log out</button>
199 </form>
200 {{else}}
201 <a href="/auth/login">Log in</a>
202 <a href="/auth/register" class="btn btn-sm">Sign up</a>
203 <a href="/about">About</a>
204 {{end}}
205 <button id="theme-toggle" class="btn-link" aria-label="Toggle dark mode" onclick="toggleTheme()" style="font-size:1.1rem;padding:0.25rem">☀</button>
206 </div>
207</nav>
208```
209
210New:
211```html
212<nav class="flex items-center justify-between px-6 py-3 border-b border-[var(--border)]">
213 <a href="/" class="flex items-center">
214 <img src="/static/img/dd-logo.svg" alt="" width="22" height="28" class="align-middle mr-1">Diffdown
215 </a>
216 <div class="flex items-center gap-4">
217 {{if .User}}
218 <a href="/" class="text-[var(--primary)] hover:underline">Documents</a>
219 <a href="/about" class="text-[var(--primary)] hover:underline">About</a>
220 <span class="text-[var(--text-muted)]">{{.User.Name}}</span>
221 <form method="post" action="/auth/logout">
222 <button type="submit" class="text-[var(--primary)] bg-transparent border-none cursor-pointer hover:underline">Log out</button>
223 </form>
224 {{else}}
225 <a href="/auth/login" class="text-[var(--primary)] hover:underline">Log in</a>
226 <a href="/auth/register" class="px-3 py-1.5 text-sm bg-[var(--primary)] text-white rounded hover:bg-[var(--primary-hover)]">Sign up</a>
227 <a href="/about" class="text-[var(--primary)] hover:underline">About</a>
228 {{end}}
229 <button id="theme-toggle" class="text-[var(--primary)] bg-transparent border-none cursor-pointer text-lg" aria-label="Toggle dark mode" onclick="toggleTheme()">☀</button>
230 </div>
231</nav>
232```
233
234- [ ] **Step 2: Replace main and alert styles**
235
236Old:
237```html
238<main>
239 {{if .Error}}
240 <div class="alert alert-error">{{.Error}}</div>
241 {{end}}
242 {{block "content" .}}{{end}}
243</main>
244```
245
246New:
247```html
248<main class="p-6">
249 {{if .Error}}
250 <div class="p-4 mb-4 bg-[var(--alert-error-bg)] border border-[var(--alert-error-border)] rounded text-[var(--danger)]">{{.Error}}</div>
251 {{end}}
252 {{block "content" .}}{{end}}
253</main>
254```
255
256- [ ] **Step 3: Update base.html to link tailwind.css instead of style.css**
257
258Old:
259```html
260<link rel="stylesheet" href="/static/css/style.css">
261```
262
263New (keep style.css for now, we'll remove later):
264```html
265<link rel="stylesheet" href="/static/css/tailwind.css">
266<link rel="stylesheet" href="/static/css/style.css">
267```
268
269- [ ] **Step 4: Test locally**
270
271Run: `make run`
272Open: `http://127.0.0.1:8080`
273Verify: Navbar, main content, and dark mode still work
274
275- [ ] **Step 5: Commit**
276
277```bash
278git add templates/base.html
279git commit -m "refactor: convert base.html to TailwindCSS classes"
280```
281
282---
283
284## Chunk 3: Replace style.css (base styles)
285
286**Files:**
287- Modify: `/Users/johnluther/projects/diffdown/templates/landing.html`
288- Modify: `/Users/johnluther/projects/diffdown/templates/documents.html`
289- Modify: `/Users/johnluther/projects/diffdown/templates/register.html`
290- Modify: `/Users/johnluther/projects/diffdown/templates/login.html`
291
292- [ ] **Step 1: Review style.css for reusable patterns**
293
294Run: `cat static/css/style.css`
295
296Identify patterns to convert:
297- `.btn`, `.btn-sm`, `.btn-outline` → Tailwind utility classes
298- `.alert`, `.alert-error` → Already handled in base.html
299- `.card` → Tailwind card styles
300- `.form-group`, `.form-label`, `.form-input` → Tailwind form styles
301
302- [ ] **Step 2: Convert landing.html**
303
304Read and convert elements to use Tailwind classes inline instead of custom CSS classes.
305
306- [ ] **Step 3: Convert documents.html**
307
308Same approach for the documents dashboard page.
309
310- [ ] **Step 4: Convert auth pages (register, login)**
311
312Convert form elements to use Tailwind classes.
313
314- [ ] **Step 5: Test all pages**
315
316Run: `make run`
317Verify: Landing, documents, login, register pages render correctly with dark mode
318
319- [ ] **Step 6: Commit**
320
321```bash
322git add templates/
323git commit -m "refactor: convert auth and dashboard pages to TailwindCSS"
324```
325
326---
327
328## Chunk 4: Replace editor.css (complex layout)
329
330**Files:**
331- Modify: `/Users/johnluther/projects/diffdown/templates/document_edit.html`
332- Modify: `/Users/johnluther/projects/diffdown/templates/document_view.html`
333
334- [ ] **Step 1: Review editor.css**
335
336Key patterns:
337- `.editor-page` → Full height flex container
338- `.editor-toolbar` → Sticky top toolbar with flex layout
339- `.editor-rich` → Rich text editor container
340- `.editor-split` → Split pane layout (editor + preview)
341- `.invite-modal` → Modal overlay and box
342
343- [ ] **Step 2: Convert document_edit.html toolbar**
344
345```html
346<!-- Old -->
347<div class="editor-toolbar">
348 <div class="breadcrumb">
349 ...
350 </div>
351 <div class="toolbar-actions">
352 ...
353 </div>
354</div>
355
356<!-- New -->
357<div class="sticky top-0 z-50 flex items-center justify-between px-4 py-2 border-b border-[var(--border)] bg-[var(--bg)]">
358 <div class="flex items-center gap-2">
359 ...
360 </div>
361 <div class="flex items-center gap-2">
362 ...
363 </div>
364</div>
365```
366
367- [ ] **Step 3: Convert editor containers**
368
369- [ ] **Step 4: Convert modal components**
370
371- [ ] **Step 5: Test editor functionality**
372
373Run: `make run`
374Create/edit a document
375Verify: Rich mode, source mode, preview, toolbar all work
376
377- [ ] **Step 6: Commit**
378
379```bash
380git add templates/document_edit.html templates/document_view.html
381git commit -m "refactor: convert editor pages to TailwindCSS classes"
382```
383
384---
385
386## Chunk 5: Replace markdown.css and diff.css
387
388**Files:**
389- Modify: `/Users/johnluther/projects/diffdown/templates/document_view.html`
390- Modify: `/Users/johnluther/projects/diffdown/templates/landing.html`
391- Create: Keep minimal overrides in a new file if needed
392
393- [ ] **Step 1: Review markdown.css**
394
395Mostly Markdown rendering styles - can keep as-is for now with minimal overrides.
396
397- [ ] **Step 2: Review diff.css**
398
399Diff view styles - can keep as-is.
400
401- [ ] **Step 3: Link tailwind.css in view templates**
402
403Add `<link rel="stylesheet" href="/static/css/tailwind.css">` to templates that don't have it.
404
405- [ ] **Step 4: Test view pages**
406
407Run: `make run`
408View a document
409Verify: Markdown renders correctly with dark mode
410
411- [ ] **Step 5: Commit**
412
413```bash
414git add templates/
415git commit -m "refactor: add TailwindCSS to view pages"
416```
417
418---
419
420## Chunk 6: Cleanup and final integration
421
422**Files:**
423- Delete: `/Users/johnluther/projects/diffdown/static/css/style.css` (after verifying no missing styles)
424- Delete: `/Users/johnluther/projects/diffdown/static/css/editor.css` (after verifying no missing styles)
425- Modify: `/Users/johnluther/projects/diffdown/templates/base.html`
426- Modify: `/Users/johnluther/projects/diffdown/package.json`
427
428- [ ] **Step 1: Check for any remaining custom CSS references**
429
430Run: `grep -r 'class="' templates/ | grep -E '\.(btn|alert|card|form)' | head -20`
431
432- [ ] **Step 2: Remove unused CSS files one by one**
433
434Start with style.css - comment out the link in base.html, test thoroughly, then delete.
435
436- [ ] **Step 3: Keep minimal base.css for CodeMirror/Milkdown overrides**
437
438Create `static/css/base.css` for:
439- CodeMirror theme overrides
440- Milkdown-specific styles
441- Any other third-party library styles
442
443- [ ] **Step 4: Update base.html to remove old CSS links**
444
445```html
446<link rel="stylesheet" href="/static/css/tailwind.css">
447<link rel="stylesheet" href="/static/css/base.css">
448```
449
450- [ ] **Step 5: Add CSS build to existing build command**
451
452```json
453"scripts": {
454 "build": "npm run build:css && npm run build:collab",
455 "build:css": "npx tailwindcss -i ./src/styles/input.css -o ./static/css/tailwind.css --minify",
456 "build:collab": "npx esbuild node_modules/prosemirror-collab/dist/index.js --bundle --format=esm --outfile=static/vendor/collab.js"
457}
458```
459
460- [ ] **Step 6: Final testing**
461
462Run: `make build && make run`
463Test all pages:
464- Landing page
465- Login/register
466- Documents list
467- Document edit (rich + source mode)
468- Document view
469- About page
470- Dark mode toggle
471
472- [ ] **Step 7: Commit final cleanup**
473
474```bash
475git add -A
476git commit -m "refactor: complete TailwindCSS migration, remove legacy CSS"
477```
478
479---
480
481## Testing Checklist
482
483- [ ] Landing page displays correctly (logged out)
484- [ ] Login page renders with proper styling
485- [ ] Register page renders with proper styling
486- [ ] Dashboard shows document list with cards
487- [ ] Dark mode toggle works on all pages
488- [ ] Document editor loads in rich mode
489- [ ] Document editor toggles to source mode
490- [ ] Preview pane renders Markdown
491- [ ] Invite modal opens/closes properly
492- [ ] Document view renders Markdown
493- [ ] About page displays correctly
494- [ ] Navbar shows correct links for logged in/out users
495
496---
497
498## Rollback Plan
499
500If issues arise:
5011. Keep old CSS files in a `static/css/legacy/` folder
5022. Restore old link in base.html: `<link rel="stylesheet" href="/static/css/style.css">`
5033. Test before committing deletions
504
505---
506
507## Notes
508
509- TailwindCSS 4.x uses a different configuration approach - verify which version is installed
510- The `data-theme` attribute approach is already implemented, Tailwind's `darkMode: 'class'` should work with it
511- CodeMirror and Milkdown may need specific overrides in a separate CSS file
512- Consider using `@layer components` in input.css for complex custom patterns