Migrating from Bootstrap to Tailwind CSS
Migrating from Bootstrap to Tailwind CSS
Last month, I migrated a production application from Bootstrap 5 to Tailwind CSS. Here’s what I learned from the experience.
Project Context
- Codebase: 50,000+ lines of code
- Framework: Laravel + Blade templates
- Duration: 3 weeks
- Bootstrap version: 5.3
- Tailwind version: 3.4
Why Migrate?
Reasons for Switching
- Bundle size: Bootstrap CSS was 150KB minified, Tailwind purged to 8KB
- Customization: Easier to customize without overriding styles
- Developer experience: Faster development with utility classes
- Consistency: Forced consistent spacing and colors
- Modern approach: Industry moving toward utility-first CSS
Initial Concerns
- Learning curve for team members
- Migration effort seemed daunting
- Breaking existing layouts
- Third-party component compatibility
Migration Strategy
Phase 1: Preparation (Week 1)
1. Audit Current Bootstrap Usage
# Find all Bootstrap classes
grep -r "class=" resources/views/ | grep -E "(btn|card|modal|nav)" > bootstrap-usage.txt
Found we used:
- Grid system (heavily)
- Cards and modals
- Forms and buttons
- Navigation components
2. Install Tailwind Alongside Bootstrap
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
Configure tailwind.config.js:
module.exports = {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
],
theme: {
extend: {
colors: {
primary: '#0d6efd', // Match Bootstrap primary
secondary: '#6c757d',
},
},
},
plugins: [],
};
Key decision: Keep Bootstrap temporarily for gradual migration.
Phase 2: Component-by-Component Migration (Week 2)
Example: Button Migration
Before (Bootstrap):
<button class="btn btn-primary btn-lg">
Submit
</button>
After (Tailwind):
<button class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-lg transition">
Submit
</button>
Created reusable Blade components:
{{-- resources/views/components/button.blade.php --}}
@props(['variant' => 'primary', 'size' => 'md'])
@php
$classes = [
'primary' => 'bg-blue-600 hover:bg-blue-700 text-white',
'secondary' => 'bg-gray-600 hover:bg-gray-700 text-white',
'success' => 'bg-green-600 hover:bg-green-700 text-white',
];
$sizes = [
'sm' => 'py-1 px-3 text-sm',
'md' => 'py-2 px-4',
'lg' => 'py-3 px-6 text-lg',
];
$class = $classes[$variant] . ' ' . $sizes[$size];
@endphp
<button {{ $attributes->merge(['class' => "$class font-semibold rounded-lg transition"]) }}>
{{ $slot }}
</button>
Usage:
<x-button variant="primary" size="lg">
Submit
</x-button>
Layout Grid Migration
Before (Bootstrap):
<div class="container">
<div class="row">
<div class="col-md-6">Column 1</div>
<div class="col-md-6">Column 2</div>
</div>
</div>
After (Tailwind):
<div class="container mx-auto px-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>Column 1</div>
<div>Column 2</div>
</div>
</div>
Phase 3: Remove Bootstrap (Week 3)
Final Cleanup
- Removed Bootstrap from
package.json - Deleted Bootstrap imports from
app.css - Ran tests to catch broken layouts
- Fixed remaining issues
npm uninstall bootstrap
Challenges & Solutions
Challenge 1: Third-Party Components
Problem: Used Bootstrap-dependent jQuery plugins
Solution: Replaced with headless UI libraries
- Bootstrap Modals → Headless UI Dialog
- Bootstrap Dropdowns → Headless UI Menu
- Bootstrap Tooltips → Tippy.js
Challenge 2: Complex Bootstrap Components
Problem: Some Bootstrap components (like carousels) were complex
Solution: Used Tailwind + AlpineJS for interactivity
<div x-data="{ current: 0 }" class="relative">
<div class="overflow-hidden">
<div class="flex transition-transform duration-300"
:style="`transform: translateX(-${current * 100}%)`">
<div class="w-full flex-shrink-0">Slide 1</div>
<div class="w-full flex-shrink-0">Slide 2</div>
<div class="w-full flex-shrink-0">Slide 3</div>
</div>
</div>
<button @click="current = current > 0 ? current - 1 : 2"
class="absolute left-4 top-1/2 -translate-y-1/2">
Previous
</button>
<button @click="current = current < 2 ? current + 1 : 0"
class="absolute right-4 top-1/2 -translate-y-1/2">
Next
</button>
</div>
Challenge 3: Team Adoption
Problem: Team unfamiliar with Tailwind
Solution:
- Created style guide with common patterns
- Pair programming sessions
- Tailwind playground for experimentation
Results
Performance Improvements
| Metric | Before (Bootstrap) | After (Tailwind) | Improvement |
|---|---|---|---|
| CSS Size | 150KB | 8KB | 94% smaller |
| First Paint | 1.2s | 0.8s | 33% faster |
| Lighthouse Score | 78 | 94 | +16 points |
Developer Experience
Positive changes:
- ✅ Faster development (no context switching to CSS files)
- ✅ More consistent spacing and colors
- ✅ Easier to make responsive designs
- ✅ Better code completion with Tailwind IntelliSense
Challenges:
- ❌ Initial learning curve
- ❌ Longer class names
- ❌ Need component extraction for complex UI
Best Practices Learned
1. Use @apply Sparingly
Bad:
.button {
@apply bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded;
}
Good: Use Blade/React components instead
2. Extract Repeated Patterns
Create reusable components for:
- Buttons
- Cards
- Form inputs
- Modals
3. Configure Theme
Extend Tailwind’s theme to match your design system:
theme: {
extend: {
colors: {
brand: {
50: '#eff6ff',
// ... your color scale
900: '#1e3a8a',
},
},
spacing: {
'128': '32rem',
'144': '36rem',
},
},
},
4. Use Plugins
Install useful plugins:
npm install -D @tailwindcss/forms @tailwindcss/typography @tailwindcss/aspect-ratio
Would I Do It Again?
Yes! The migration was worth it for:
- Performance gains - 94% smaller CSS bundle
- Development speed - Faster to build new features
- Consistency - Forced design system adherence
- Modern stack - Better tooling and community support
Tips for Your Migration
- Start small - Migrate one component at a time
- Keep Bootstrap temporarily - Don’t remove until fully migrated
- Create components - Extract repeated patterns immediately
- Document patterns - Create a style guide for your team
- Budget time - Took us 3 weeks for 50K lines of code
- Test thoroughly - Check responsive breakpoints
Conclusion
Migrating from Bootstrap to Tailwind was a significant undertaking, but the results speak for themselves. The smaller bundle size, faster development speed, and improved developer experience made it worthwhile.
If you’re considering the switch, my advice: Do it incrementally, create reusable components, and involve your team in the process.