TailwindCSS Migration Implementation Plan#
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.
Goal: Replace the ~1000 lines of custom CSS with TailwindCSS utility classes, while preserving the existing design and dark mode support.
Architecture:
- Install TailwindCSS as a dev dependency with PostCSS
- Create a minimal
tailwind.config.jsthat extends the default theme with the existing color palette - Keep a small
base.cssfor critical custom styles that can't be replaced (e.g., CodeMirror overrides, Milkdown-specific styles) - Replace HTML class names with Tailwind utility classes in templates
- Use
@applyfor complex patterns that appear multiple times
Tech Stack: TailwindCSS 4.x, PostCSS, existing esbuild for CSS bundling
Current CSS Inventory#
| File | Lines | Purpose |
|---|---|---|
static/css/style.css |
427 | Base styles, navbar, cards, buttons, forms, alerts |
static/css/editor.css |
419 | Editor layout, toolbar, modals, split pane |
static/css/markdown.css |
84 | Markdown preview rendering |
static/css/diff.css |
60 | Diff view styling |
| Total | 990 |
Chunk 1: Setup TailwindCSS#
Files:
-
Modify:
/Users/johnluther/projects/diffdown/package.json -
Create:
/Users/johnluther/projects/diffdown/tailwind.config.js -
Create:
/Users/johnluther/projects/diffdown/postcss.config.js -
Create:
/Users/johnluther/projects/diffdown/src/styles/input.css -
Step 1: Install TailwindCSS and dependencies
Run: npm install -D tailwindcss postcss autoprefixer
- Step 2: Initialize Tailwind config
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./templates/**/*.html",
"./static/**/*.js",
],
darkMode: 'class', // Use data-theme attribute for dark mode
theme: {
extend: {
colors: {
bg: 'var(--bg)',
'bg-card': 'var(--bg-card)',
text: 'var(--text)',
'text-muted': 'var(--text-muted)',
border: 'var(--border)',
primary: 'var(--primary)',
'primary-hover': 'var(--primary-hover)',
danger: 'var(--danger)',
success: 'var(--success)',
'code-bg': 'var(--code-bg)',
'alert-error-bg': 'var(--alert-error-bg)',
'alert-error-border': 'var(--alert-error-border)',
},
fontFamily: {
sans: ['"Barlow"', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
heading: ['"Lexend"', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
mono: ['"JetBrains Mono"', '"Fira Code"', '"Cascadia Code"', 'monospace'],
},
borderRadius: {
DEFAULT: '6px',
},
},
},
plugins: [],
}
- Step 3: Create PostCSS config
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
- Step 4: Create input.css for Tailwind directives
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Keep custom font import */
@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);
/* CSS variables - kept for backward compatibility */
:root {
--bg: #fafafa;
--bg-card: #fff;
--text: #1a1a2e;
--text-muted: #6b7280;
--border: #e5e7eb;
--primary: #2563eb;
--primary-hover: #1d4ed8;
--danger: #dc2626;
--success: #16a34a;
--code-bg: #f3f4f6;
--alert-error-bg: #fef2f2;
--alert-error-border: #fecaca;
}
[data-theme="dark"] {
--bg: #0d1117;
--bg-card: #161b22;
--text: #e6edf3;
--text-muted: #ededed;
--text-secondary: #d4d4d4;
--border: #30363d;
--code-bg: #1f2428;
--primary: #388bfd;
--primary-hover: #58a6ff;
--danger: #f85149;
--success: #3fb950;
--alert-error-bg: #1c0608;
--alert-error-border: #6e1c20;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #0d1117;
--bg-card: #161b22;
--text: #e6edf3;
--text-muted: #ededed;
--text-secondary: #d4d4d4;
--border: #30363d;
--code-bg: #1f2428;
--primary: #388bfd;
--primary-hover: #58a6ff;
--danger: #f85149;
--success: #3fb950;
--alert-error-bg: #1c0608;
--alert-error-border: #6e1c20;
}
}
- Step 5: Add build script to package.json
"scripts": {
"build:css": "npx tailwindcss -i ./src/styles/input.css -o ./static/css/tailwind.css",
"build:collab": "npx esbuild node_modules/prosemirror-collab/dist/index.js --bundle --format=esm --outfile=static/vendor/collab.js",
"build": "npm run build:css && npm run build:collab"
}
- Step 6: Build TailwindCSS
Run: npm run build:css
Expected: Creates static/css/tailwind.css with Tailwind utilities
- Step 7: Commit
git add package.json tailwind.config.js postcss.config.js src/styles/input.css static/css/tailwind.css
git commit -m "feat: add TailwindCSS setup"
Chunk 2: Replace CSS in base.html#
Files:
-
Modify:
/Users/johnluther/projects/diffdown/templates/base.html -
Step 1: Replace navbar styles with Tailwind classes
Old:
<nav class="navbar">
<a href="/" class="logo">
<img src="/static/img/dd-logo.svg" alt="" width="22" height="28" style="vertical-align:middle;margin-right:0.4rem">Diffdown
</a>
<div class="nav-right">
{{if .User}}
<a href="/">Documents</a>
<a href="/about">About</a>
<span class="nav-user">{{.User.Name}}</span>
<form method="post" action="/auth/logout" style="display:inline">
<button type="submit" class="btn-link">Log out</button>
</form>
{{else}}
<a href="/auth/login">Log in</a>
<a href="/auth/register" class="btn btn-sm">Sign up</a>
<a href="/about">About</a>
{{end}}
<button id="theme-toggle" class="btn-link" aria-label="Toggle dark mode" onclick="toggleTheme()" style="font-size:1.1rem;padding:0.25rem">☀</button>
</div>
</nav>
New:
<nav class="flex items-center justify-between px-6 py-3 border-b border-[var(--border)]">
<a href="/" class="flex items-center">
<img src="/static/img/dd-logo.svg" alt="" width="22" height="28" class="align-middle mr-1">Diffdown
</a>
<div class="flex items-center gap-4">
{{if .User}}
<a href="/" class="text-[var(--primary)] hover:underline">Documents</a>
<a href="/about" class="text-[var(--primary)] hover:underline">About</a>
<span class="text-[var(--text-muted)]">{{.User.Name}}</span>
<form method="post" action="/auth/logout">
<button type="submit" class="text-[var(--primary)] bg-transparent border-none cursor-pointer hover:underline">Log out</button>
</form>
{{else}}
<a href="/auth/login" class="text-[var(--primary)] hover:underline">Log in</a>
<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>
<a href="/about" class="text-[var(--primary)] hover:underline">About</a>
{{end}}
<button id="theme-toggle" class="text-[var(--primary)] bg-transparent border-none cursor-pointer text-lg" aria-label="Toggle dark mode" onclick="toggleTheme()">☀</button>
</div>
</nav>
- Step 2: Replace main and alert styles
Old:
<main>
{{if .Error}}
<div class="alert alert-error">{{.Error}}</div>
{{end}}
{{block "content" .}}{{end}}
</main>
New:
<main class="p-6">
{{if .Error}}
<div class="p-4 mb-4 bg-[var(--alert-error-bg)] border border-[var(--alert-error-border)] rounded text-[var(--danger)]">{{.Error}}</div>
{{end}}
{{block "content" .}}{{end}}
</main>
- Step 3: Update base.html to link tailwind.css instead of style.css
Old:
<link rel="stylesheet" href="/static/css/style.css">
New (keep style.css for now, we'll remove later):
<link rel="stylesheet" href="/static/css/tailwind.css">
<link rel="stylesheet" href="/static/css/style.css">
- Step 4: Test locally
Run: make run
Open: http://127.0.0.1:8080
Verify: Navbar, main content, and dark mode still work
- Step 5: Commit
git add templates/base.html
git commit -m "refactor: convert base.html to TailwindCSS classes"
Chunk 3: Replace style.css (base styles)#
Files:
-
Modify:
/Users/johnluther/projects/diffdown/templates/landing.html -
Modify:
/Users/johnluther/projects/diffdown/templates/documents.html -
Modify:
/Users/johnluther/projects/diffdown/templates/register.html -
Modify:
/Users/johnluther/projects/diffdown/templates/login.html -
Step 1: Review style.css for reusable patterns
Run: cat static/css/style.css
Identify patterns to convert:
-
.btn,.btn-sm,.btn-outline→ Tailwind utility classes -
.alert,.alert-error→ Already handled in base.html -
.card→ Tailwind card styles -
.form-group,.form-label,.form-input→ Tailwind form styles -
Step 2: Convert landing.html
Read and convert elements to use Tailwind classes inline instead of custom CSS classes.
- Step 3: Convert documents.html
Same approach for the documents dashboard page.
- Step 4: Convert auth pages (register, login)
Convert form elements to use Tailwind classes.
- Step 5: Test all pages
Run: make run
Verify: Landing, documents, login, register pages render correctly with dark mode
- Step 6: Commit
git add templates/
git commit -m "refactor: convert auth and dashboard pages to TailwindCSS"
Chunk 4: Replace editor.css (complex layout)#
Files:
-
Modify:
/Users/johnluther/projects/diffdown/templates/document_edit.html -
Modify:
/Users/johnluther/projects/diffdown/templates/document_view.html -
Step 1: Review editor.css
Key patterns:
-
.editor-page→ Full height flex container -
.editor-toolbar→ Sticky top toolbar with flex layout -
.editor-rich→ Rich text editor container -
.editor-split→ Split pane layout (editor + preview) -
.invite-modal→ Modal overlay and box -
Step 2: Convert document_edit.html toolbar
<!-- Old -->
<div class="editor-toolbar">
<div class="breadcrumb">
...
</div>
<div class="toolbar-actions">
...
</div>
</div>
<!-- New -->
<div class="sticky top-0 z-50 flex items-center justify-between px-4 py-2 border-b border-[var(--border)] bg-[var(--bg)]">
<div class="flex items-center gap-2">
...
</div>
<div class="flex items-center gap-2">
...
</div>
</div>
-
Step 3: Convert editor containers
-
Step 4: Convert modal components
-
Step 5: Test editor functionality
Run: make run
Create/edit a document
Verify: Rich mode, source mode, preview, toolbar all work
- Step 6: Commit
git add templates/document_edit.html templates/document_view.html
git commit -m "refactor: convert editor pages to TailwindCSS classes"
Chunk 5: Replace markdown.css and diff.css#
Files:
-
Modify:
/Users/johnluther/projects/diffdown/templates/document_view.html -
Modify:
/Users/johnluther/projects/diffdown/templates/landing.html -
Create: Keep minimal overrides in a new file if needed
-
Step 1: Review markdown.css
Mostly Markdown rendering styles - can keep as-is for now with minimal overrides.
- Step 2: Review diff.css
Diff view styles - can keep as-is.
- Step 3: Link tailwind.css in view templates
Add <link rel="stylesheet" href="/static/css/tailwind.css"> to templates that don't have it.
- Step 4: Test view pages
Run: make run
View a document
Verify: Markdown renders correctly with dark mode
- Step 5: Commit
git add templates/
git commit -m "refactor: add TailwindCSS to view pages"
Chunk 6: Cleanup and final integration#
Files:
-
Delete:
/Users/johnluther/projects/diffdown/static/css/style.css(after verifying no missing styles) -
Delete:
/Users/johnluther/projects/diffdown/static/css/editor.css(after verifying no missing styles) -
Modify:
/Users/johnluther/projects/diffdown/templates/base.html -
Modify:
/Users/johnluther/projects/diffdown/package.json -
Step 1: Check for any remaining custom CSS references
Run: grep -r 'class="' templates/ | grep -E '\.(btn|alert|card|form)' | head -20
- Step 2: Remove unused CSS files one by one
Start with style.css - comment out the link in base.html, test thoroughly, then delete.
- Step 3: Keep minimal base.css for CodeMirror/Milkdown overrides
Create static/css/base.css for:
-
CodeMirror theme overrides
-
Milkdown-specific styles
-
Any other third-party library styles
-
Step 4: Update base.html to remove old CSS links
<link rel="stylesheet" href="/static/css/tailwind.css">
<link rel="stylesheet" href="/static/css/base.css">
- Step 5: Add CSS build to existing build command
"scripts": {
"build": "npm run build:css && npm run build:collab",
"build:css": "npx tailwindcss -i ./src/styles/input.css -o ./static/css/tailwind.css --minify",
"build:collab": "npx esbuild node_modules/prosemirror-collab/dist/index.js --bundle --format=esm --outfile=static/vendor/collab.js"
}
- Step 6: Final testing
Run: make build && make run
Test all pages:
-
Landing page
-
Login/register
-
Documents list
-
Document edit (rich + source mode)
-
Document view
-
About page
-
Dark mode toggle
-
Step 7: Commit final cleanup
git add -A
git commit -m "refactor: complete TailwindCSS migration, remove legacy CSS"
Testing Checklist#
- Landing page displays correctly (logged out)
- Login page renders with proper styling
- Register page renders with proper styling
- Dashboard shows document list with cards
- Dark mode toggle works on all pages
- Document editor loads in rich mode
- Document editor toggles to source mode
- Preview pane renders Markdown
- Invite modal opens/closes properly
- Document view renders Markdown
- About page displays correctly
- Navbar shows correct links for logged in/out users
Rollback Plan#
If issues arise:
- Keep old CSS files in a
static/css/legacy/folder - Restore old link in base.html:
<link rel="stylesheet" href="/static/css/style.css"> - Test before committing deletions
Notes#
- TailwindCSS 4.x uses a different configuration approach - verify which version is installed
- The
data-themeattribute approach is already implemented, Tailwind'sdarkMode: 'class'should work with it - CodeMirror and Milkdown may need specific overrides in a separate CSS file
- Consider using
@layer componentsin input.css for complex custom patterns