This is a debate I have with myself on every Bootstrap project. Let me share the framework I've landed on after building dozens of Bootstrap apps.

Bootstrap 5 Utilities Are Genuinely Good

Bootstrap 5's utility classes cover a lot of ground. Spacing, flexbox, grid, colors, typography, display, positioning, borders, shadows — you can build surprisingly complete layouts without a single line of custom CSS.

<!-- This is entirely utilities — and it's fine -->
<div class="d-flex align-items-center gap-3 p-4 bg-white rounded-3 shadow-sm">
  <div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center fw-bold"
    style="width:48px;height:48px;">GS</div>
  <div>
    <h6 class="fw-bold mb-0">Gagan Singh</h6>
    <p class="text-muted small mb-0">Founder</p>
  </div>
  <a href="#" class="btn btn-sm ms-auto text-white" style="background:#fd4766;">Follow</a>
</div>

Is that HTML verbose? Yes. Does it work without touching a stylesheet? Also yes. For a component you'll only use once or twice, that tradeoff is often worth it.

When Utilities Get Messy

The problem hits when you need the same combination of classes repeatedly. Five cards all with card border-0 shadow-sm rounded-3 overflow-hidden — that's fine. But when you have thirty cards and you want to change the shadow on all of them, you're doing thirty find-and-replaces.

The other problem is complex visual states that utilities can't express:

/* You can't do this with Bootstrap utilities alone */
.nav-link::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 2px;
  background: #fd4766;
  transform: scaleX(0);
  transition: transform 0.2s ease;
}

.nav-link:hover::after,
.nav-link.active::after {
  transform: scaleX(1);
}

Pseudo-elements, complex transitions, custom clip-paths, CSS animations — these all require custom CSS.

My Working Rules

Use Bootstrap utilities for:

  • All spacing (margin, padding, gap) — mt-3, px-4, gap-2
  • Display and flex — d-flex, align-items-center, justify-content-between
  • Width/height shortcuts — w-100, h-100, min-vh-100
  • Color and background — text-muted, bg-white, text-success
  • Typography — fw-bold, small, fs-5, text-truncate
  • Borders and radius — border-0, rounded-3, shadow-sm
  • Visibility and position — d-none d-md-block, position-relative

Write custom CSS for:

  • Reusable component styles used more than 3 times
  • Hover effects with transitions
  • Pseudo-elements (::before, ::after)
  • CSS animations and keyframes
  • Custom scrollbar styling
  • Anything requiring more than 5-6 utilities to express
  • Brand-specific overrides of Bootstrap components

The Hybrid Approach

The cleanest codebases I've worked on mix both:

// Custom class for reusable pattern
.template-card {
  border-radius: 12px;
  overflow: hidden;
  transition: transform 0.25s ease, box-shadow 0.25s ease;

  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  }
}
<!-- Bootstrap utilities handle layout, custom class handles the specific pattern -->
<div class="template-card border-0 shadow-sm">
  <img src="..." class="w-100" alt="">
  <div class="p-3">
    <!-- content -->
  </div>
</div>

The component class captures the specific, reusable pattern. Utilities handle the generic layout needs.

The Bootstrap 5 Utility API

One thing people miss: Bootstrap 5 has a Utility API that lets you generate your own utility classes from Sass. Need .cursor-pointer? Add it to the utility map:

$utilities: map-merge(
  $utilities,
  (
    "cursor": (
      property: cursor,
      values: auto pointer default grab
    )
  )
);

Now you have .cursor-pointer, .cursor-grab etc. This bridges the gap between utility-first and custom CSS — you get new utilities without leaving Bootstrap's system.

Bottom Line

Neither approach in isolation. Use utilities aggressively for layout and spacing. Write custom CSS when you have a genuine component pattern that repeats or when you need CSS features that utilities can't express. Extract repeated utility patterns into named classes. Use Bootstrap's Utility API for common gaps.

The developers who write the most maintainable Bootstrap code are the ones who know intuitively which tier to reach for — and switch between them without ideology.

Frequently Asked Questions

Use utility classes for spacing, display, flexbox, alignment and color — Bootstrap's utilities cover these well. Write custom CSS for complex components, unique animations, specific hover effects and anything that requires more than 4-5 utility classes to express cleanly.
Bootstrap 5's Utility API lets you generate custom utility classes from a Sass configuration. Add your own utilities or modify existing ones: $utilities: map-merge($utilities, ('cursor': (property: cursor, values: (auto, pointer, default)))). This generates .cursor-auto, .cursor-pointer etc.
Not inherently. The concern is readability — 15 utility classes on one element is hard to scan. If you find yourself writing the same 8 classes on every card, extract them into a component class. Otherwise utility composition is the intended Bootstrap 5 pattern.
No. Bootstrap utilities are excellent for layout and spacing but don't cover everything. Complex animations, pseudo-elements (::before, ::after), custom scrollbars, clip-path, and many other CSS properties have no utility equivalent. Custom CSS is always necessary for non-trivial projects.

Related Comparisons

Already Decided on Bootstrap?

Get a complete Angular 21 + Bootstrap 5 admin dashboard template — production ready.

Browse Templates →

Use code FIRST30 for 30% off.