Back to Articles
December 5, 2024 7 min

Migrating from Bootstrap to Tailwind CSS

#CSS
#Tailwind
#Experience

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

  1. Bundle size: Bootstrap CSS was 150KB minified, Tailwind purged to 8KB
  2. Customization: Easier to customize without overriding styles
  3. Developer experience: Faster development with utility classes
  4. Consistency: Forced consistent spacing and colors
  5. 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

  1. Removed Bootstrap from package.json
  2. Deleted Bootstrap imports from app.css
  3. Ran tests to catch broken layouts
  4. 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

MetricBefore (Bootstrap)After (Tailwind)Improvement
CSS Size150KB8KB94% smaller
First Paint1.2s0.8s33% faster
Lighthouse Score7894+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:

  1. Performance gains - 94% smaller CSS bundle
  2. Development speed - Faster to build new features
  3. Consistency - Forced design system adherence
  4. Modern stack - Better tooling and community support

Tips for Your Migration

  1. Start small - Migrate one component at a time
  2. Keep Bootstrap temporarily - Don’t remove until fully migrated
  3. Create components - Extract repeated patterns immediately
  4. Document patterns - Create a style guide for your team
  5. Budget time - Took us 3 weeks for 50K lines of code
  6. 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.