iMessage-like Chat UI: Vanilla HTML/CSS/JS Design Decision
Date: November 2025 Project: Chat App PWA Decision: Use vanilla HTML/CSS/JS instead of frameworks (React, Vue, Next.js, etc.)
Context
Building a modern, native-feeling chat interface for iOS that works as a Progressive Web App. Goal is to achieve iMessage-like smoothness and polish while maintaining simplicity and performance.
Decision: Vanilla HTML/CSS/JS
We chose to build with no frameworks - pure web standards only.
Technology Stack
- HTML5 (semantic, accessible)
- CSS3 (custom properties, flexbox, animations)
- Vanilla JavaScript (ES6+ classes)
- Service Worker (PWA offline support)
- systemd (server persistence)
Total bundle size: ~20KB (HTML + CSS + JS combined)
Why Vanilla Won
1. Performance
Our metrics:
- First load: 20KB total
- 60fps animations
- Instant subsequent loads (service worker)
- No framework overhead
Framework comparison:
- React PWA: 130KB+ minimum (framework + React-DOM)
- Next.js: 200KB+ (framework + routing + runtime)
- Vue: 80KB+ (smaller but still significant)
- Svelte: 10-30KB (compiles away, but adds build complexity)
For a single-page chat interface, this overhead buys us nothing.
2. Simplicity & Control
What we have:
- View source = actual code running
- No build step required (Python static server works)
- No transpilation, bundling, or toolchain
- Full control over every interaction
- Easy to debug (browser DevTools show real code)
Framework complexity we avoided:
- npm dependencies (hundreds of packages)
- Build tools (webpack, vite, rollup)
- Transpilation (Babel, SWC)
- Framework-specific patterns and lifecycles
- Hot module replacement quirks
- Source map debugging
3. Longevity & Maintenance
Vanilla advantages:
- No framework version migrations (React 17→18→19)
- No breaking changes from dependencies
- No security vulnerabilities in node_modules
- Code works the same in 5 years
- Web standards evolve slowly and compatibly
Framework maintenance burden:
- Weekly npm security alerts
- Major version updates requiring rewrites
- Deprecation warnings
- Ecosystem churn (new best practices every year)
4. PWA & iOS Optimization
Our setup:
- Direct control over service worker caching
- Native iOS meta tags (no framework abstraction)
- Precise control over touch events, scrolling, animations
- No framework-specific PWA quirks
Framework PWA challenges:
- Generated service workers (less control)
- Framework hydration affecting perceived performance
- Extra complexity in offline strategies
- Framework routing conflicting with PWA navigation
5. Development Speed (For This Project)
Our approach:
- Write HTML → Write CSS → Write JS → Done
- Test in browser → Deploy
- Bug appears → View source → Fix → Refresh
Framework approach would require:
- Setup toolchain (create-react-app, vite, etc.)
- Learn framework patterns (hooks, components, reactivity)
- Configure build process
- Deal with development vs production differences
- Manage state management library (Redux, Zustand, etc.)
For a single-page chat UI, vanilla was faster to build and iterate.
Where Frameworks Would Win
We're honest about the trade-offs. Frameworks would be better if we had:
1. Complex State Management
Would benefit from React/Vue if:
- Multi-user chat with real-time sync
- Offline message queue with conflict resolution
- Complex user authentication state
- Message editing/deletion with optimistic updates
- Thread replies with nested state
- Read receipts and typing indicators across users
Current reality:
- Single chat view
- Simulated responses (no real state)
- No backend yet
- Simple message append operations
2. Many Reusable Components
Would benefit from components if:
- 20+ different UI components
- Complex composition patterns
- Shared component library across multiple apps
Current reality:
- Single page with simple structure
- Minimal UI variation
- No component reuse needed
3. Routing & Multiple Views
Would benefit from framework routing if:
- Chat list view
- Individual chat view
- Settings page
- Profile pages
- Search results
- Media galleries
Current reality:
- Single chat view
- No navigation needed
- No URL routing required
4. Type Safety at Scale
Would benefit from TypeScript + React if:
- Large team (5+ developers)
- Complex data models
- Many API integrations
- Refactoring frequently
Current reality:
- Small codebase (< 500 lines total)
- Simple data structures
- Automated test coverage (20 smoke tests)
- Easy to understand entire codebase
5. Server-Side Rendering
Would benefit from Next.js if:
- SEO critical (public chat archives)
- Initial load performance critical
- Server-rendered user content
Current reality:
- Private chat app (no SEO needed)
- PWA loads instantly after first visit
- Client-only rendering is fine
When To Reconsider
Red Flags That Framework Might Be Worth It
State complexity:
- Need Redux-level state management
- Optimistic updates with rollback
- Complex offline sync logic
Scale:
- 10+ different views/pages
- 50+ reusable components
- Multiple developers working simultaneously
Backend integration:
- Complex WebSocket state management
- Real-time multi-user features
- Message conflict resolution
Team:
- New developers unfamiliar with vanilla patterns
- Team already expert in a framework
- Need hiring pool (more React devs than vanilla experts)
Green Lights To Stay Vanilla
Simple additions:
- Add backend API (fetch/WebSocket work fine in vanilla)
- Add more chat features (message editing, reactions, etc.)
- Improve animations and polish
- Add voice messages, images
- Implement read receipts
As long as:
- Single-page or simple multi-page structure
- State complexity remains low-to-medium
- Performance is critical
- Team is small (1-3 developers)
Current Metrics (Baseline for Future Comparison)
Performance
- Bundle size: ~20KB total
- First contentful paint: < 100ms (after initial load)
- Time to interactive: < 100ms
- Animation frame rate: 60fps consistently
- Service worker cache size: < 50KB
Code Metrics
- Total lines of code: ~450 lines
- HTML: ~130 lines
- CSS: ~200 lines
- JS: ~120 lines
- Files: 8 core files
- Dependencies: 0 (zero)
- Build time: 0ms (no build)
Test Coverage
- 20 automated smoke tests
- All critical paths covered
- Tests run in < 5 seconds
- Pre-commit hook prevents regressions
Development Velocity
- Time to build initial version: 4 hours
- Time to add new feature: 15-30 minutes
- Time to debug issue: 5-10 minutes
- Deploy time: instant (systemd reload)
Lessons Learned
What Worked Well
- Direct DOM manipulation is fine - No virtual DOM needed for this
- CSS is powerful - Modern CSS handles 90% of what frameworks do
- Native events are sufficient - No need for synthetic events
- Service workers are straightforward - Framework abstraction adds little value
- Testing is easier - No mocking framework internals
What Was Harder Than Framework
- No component reuse patterns - Had to write custom patterns for message bubbles
- Manual DOM updates - Had to track what needs updating ourselves
- No developer tooling - No React DevTools, Vue DevTools equivalents
What We'd Do Different
- Consider TypeScript - Type safety without framework overhead
- Maybe use a micro-library - Something like Preact (3KB) if we add complexity
- Better state patterns - Could use observer pattern or custom state manager
Recommendation for Future
Continue with Vanilla If:
- ✅ Chat remains single-view or simple multi-view
- ✅ Performance is priority
- ✅ Team stays small (< 3 developers)
- ✅ State complexity stays manageable
- ✅ Want zero dependencies to maintain
Switch to Framework If:
- ❌ State becomes too complex (multi-user sync, offline queue)
- ❌ Need 10+ different views with routing
- ❌ Team grows and needs component patterns
- ❌ Performance stops being critical
- ❌ Want faster hiring (React developers are common)
Hybrid Approach (Consider Later):
- Keep vanilla for chat UI (performance critical)
- Use framework for admin panel / settings (less critical)
- Web Components for shared pieces (framework-agnostic)
Alternatives Considered
1. React
Pros: Huge ecosystem, familiar to many devs, great tooling Cons: 130KB+ overhead, build complexity, overkill for single view Verdict: Not worth it for current scope
2. Vue
Pros: Smaller than React, simpler learning curve, good reactivity Cons: Still 80KB+, build step required, unnecessary for our needs Verdict: Better than React but still overkill
3. Svelte
Pros: Compiles away (small bundle), great DX, reactive by default Cons: Build step required, less mature ecosystem, still learning curve Verdict: Most compelling framework option, but vanilla is still simpler
4. Next.js
Pros: Full-stack, great DX, SSR/SSG capabilities Cons: 200KB+, massive overkill, server complexity we don't need Verdict: Wrong tool for this job
5. Preact
Pros: Only 3KB, React-compatible API, fast Cons: Still need JSX build step, adds complexity we don't need yet Verdict: Good middle ground if vanilla becomes too complex
6. Alpine.js / htmx
Pros: Minimal, progressive enhancement, no build Cons: Still external dependencies, limited for complex UIs Verdict: Interesting but vanilla gives us more control
Technical Deep Dive: Why Performance Matters
Mobile Performance Reality
- iPhone Safari on cellular: Every KB matters
- Service worker cache is limited: Smaller = better
- Parse/compile time: Vanilla JS is instant, frameworks take 100-300ms
- Memory: Frameworks use more heap, causing GC pauses
Our Performance Wins
- Zero framework parsing - Browser runs our code directly
- Minimal cache footprint - More room for message cache
- No virtual DOM diffing - Direct DOM updates are faster for our use case
- CSS animations - GPU accelerated, no JS overhead
- Lazy loading - Only load what we need, when we need it
Real-World Impact
- App feels instant on 4G
- Works smoothly on older iPhones (iPhone 8+)
- Battery efficient (no framework overhead running)
- Can cache more messages offline (small app cache)
Conclusion
For this chat app, vanilla HTML/CSS/JS is objectively the right choice.
We get:
- Better performance
- Simpler codebase
- Full control
- Zero dependencies
- Easier debugging
- Longer lifespan
We sacrifice:
- Component reuse patterns (not needed yet)
- Framework ecosystem (not needed yet)
- Common hiring pool (team is small)
Re-evaluate when:
- State complexity increases significantly
- Multi-view routing becomes necessary
- Team grows beyond 3 developers
- Performance stops being top priority
Until then: Keep it vanilla. Keep it simple. Keep it fast.
Last updated: November 2025 Next review: When adding backend integration or when team/scope changes significantly