React vs. Bootstrap: 74 KB of CSS for 420 Routes, Zero jQuery
React is a UI library, Bootstrap a CSS framework — the real fork is Tailwind vs. Bootstrap, measured against this site's single 74 KB stylesheet and zero jQuery dependency.
React and Bootstrap are not competitors. React is a JavaScript library for building UI; Bootstrap is a CSS framework with jQuery-powered components. The comparison that actually decides anything is Tailwind vs. Bootstrap — which CSS approach to pair with React. This storefront runs React (via Next.js) with Tailwind, and its entire production stylesheet, covering all 428 routes, compiles to one 74 KB file with zero jQuery in the dependency tree.
Two categories, one search query
"React vs. Bootstrap" gets typed by people asking two different questions, and conflating them is where most confusion starts.
The first question is real: "I'm building a React app — do I style it with Bootstrap's classes or with Tailwind's?" That's a CSS-framework decision, and it's the one this post answers.
The second question is a false start: "Can I use Bootstrap's components inside React?" You can, via react-bootstrap or reactstrap — wrapper libraries that re-implement Bootstrap's JavaScript (modals, dropdowns, carousels) as React components instead of jQuery plugins, because Bootstrap's own JS manipulates the DOM directly and directly-manipulated DOM nodes are exactly what React's virtual DOM diffing doesn't expect. Reaching for one of those wrappers isn't "React vs. Bootstrap" — it's already decided in Bootstrap's favor and is asking how to make it coexist.
This codebase never reaches for either wrapper. There's no react-bootstrap line in package.json, no bootstrap line, no jquery line — grepping the manifest for all three returns nothing. What's here instead:
// package.json — the actual CSS-and-component stack
"dependencies": {
"react": "...",
"next": "...",
"tailwindcss": "...",
"lucide-react": "..."
}
Tailwind for styling, lucide-react for icons, no jQuery, no Popper.js, no separate JS bundle for interactive chrome. That absence is the first data point, and it's worth explaining why it holds.
What "styling with Bootstrap" actually ships
Bootstrap's component model is class-based and pre-compiled: .btn.btn-primary resolves to a fixed rule written once in bootstrap.min.css, shared by every button on every Bootstrap site that hasn't overridden it. Customizing beyond the theme's Sass variables means overriding those rules with more specific ones, or recompiling the whole framework from source.
Tailwind's model is utility-first and generated per-project: a component's look lives in the classes on the element itself, and the build tool emits only the rules those classes actually use. Here's the entire CTA button atom this storefront renders everywhere — pricing, hero, footer, every landing page in the catalog:
// src/components/atoms/Button.tsx
const base =
"inline-flex items-center justify-center gap-2 rounded-lg px-6 py-3 text-base font-medium shadow-xs duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2";
const variants: Record<ButtonVariant, string> = {
primary: "bg-primary text-white hover:bg-primary-600",
outline:
"border border-stroke-tertiary bg-white text-text-color hover:bg-gray-50 hover:text-gray-800",
dark: "bg-gray-900 text-white hover:bg-gray-800",
};
No .btn class exists anywhere in this project. There's nothing to override, because there's no shared rule to begin with — each variant is its own list of utilities, composed at the call site. The equivalent in Bootstrap would be three theme-color overrides plus a _buttons.scss recompile the moment the design needed a fourth variant that wasn't primary/secondary/outline/link.
| React + Tailwind (this site) | React + Bootstrap | |
|---|---|---|
| Styling unit | Utility classes on the element | Pre-defined component classes (.btn, .card, .navbar) |
| Customizing a variant | Add/change utility classes at the call site | Override CSS specificity, or recompile Sass with new variables |
| Interactive components (modal, dropdown) | Plain React state (useState, no library) | jQuery plugins, or a React wrapper library replacing them |
| Design tokens | @theme block in globals.css (CSS-first, Tailwind v4) | Sass $variables compiled ahead of time |
| Unused styles in the shipped CSS | None — only classes actually used in JSX are emitted | Every component's CSS ships whether used or not, unless manually purged |
What's actually in the shipped CSS
npm run build compiles this storefront's entire style surface — every atom, molecule, organism, and page across 428 routes (420 statically prerendered, 8 rendered on demand) — into a single stylesheet:
$ wc -c .next/static/chunks/*.css
75849 .next/static/chunks/1vzlp6kn846fm.css
$ gzip -c .next/static/chunks/*.css | wc -c
13675
One file, 74 KB raw, 13.4 KB gzipped, 1,153 rules, for the home page, the pricing page, 111 product pages, all 141 blog posts, the dashboard, and every one of this catalog's landing page templates. Tailwind's build step scans the JSX for class names actually used and generates exactly those rules — nothing for a .carousel-indicators class this site never writes, nothing for an .accordion-flush variant no component reaches for.
A Bootstrap-based project pays the inverse cost by default: import the framework and the compiled CSS includes every component's rules, including the ones the page never renders, unless someone sets up PurgeCSS (or Bootstrap's own tree-shaking build) to strip the unused parts back out — an extra build step Tailwind doesn't need because it never emits the unused rules in the first place.
Where the design tokens live
Bootstrap's theme is a set of Sass variables ($primary, $border-radius, $font-family-base) compiled into the framework ahead of time — changing one means recompiling. Tailwind v4 moved the equivalent into plain CSS, no Sass required:
/* src/app/globals.css — CSS-first config, no build-time Sass step */
@theme {
--font-sans: var(--font-outfit), ui-sans-serif, system-ui, sans-serif;
--color-primary: #465fff;
--color-primary-25: #f2f7ff;
--color-primary-50: #ecf3ff;
/* ...primary-100 through primary-950 */
}
Every bg-primary, text-primary-600, border-primary-200 utility in the codebase reads from that one block. There's a real cost worth naming honestly: Tailwind v4's @theme values don't hot-reload under Turbopack, so a color-token edit needs a dev-server restart before new utilities like bg-primary generate — the kind of build-tool quirk that doesn't show up in a features comparison but does show up in a changelog.
This storefront's component layer is small by design: 7 atoms (Button, Container, SectionHeading, and four more) and 41 molecules compose every organism on every page. Bootstrap ships its component set pre-built at a much larger surface area — dozens of components, most unused on any single page — which is the tradeoff underneath the CSS-size numbers above: build a small set of primitives against real content, or import a large set and use a fraction of it.
Where Bootstrap is still the right call
None of this makes Bootstrap the wrong tool everywhere. It's the better choice when:
- There's no build step, or can't be one. Bootstrap via CDN
<link>works in a static HTML page with zero tooling. Tailwind's utility model needs a build (or the Play CDN, which isn't meant for production). - The team already knows Bootstrap's class vocabulary and shipping consistent UI matters more than bundle size —
.d-flex.justify-content-betweenis instantly readable to anyone who's used Bootstrap before. - The product is an internal tool with a tiny user base, where 150–200 KB of framework CSS costs nothing anyone will notice.
What doesn't hold up is the specific pairing this codebase avoided: React's component re-renders fighting Bootstrap's own DOM manipulation in its bundled JavaScript. That's a real, well-documented failure mode (a Bootstrap modal's jQuery-managed classes and a React re-render disagreeing about what's in the DOM), not a matter of taste — it's the reason react-bootstrap exists as a from-scratch reimplementation rather than a thin wrapper around Bootstrap's own JS.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Bootstrap's dropdown/modal JS does nothing in a React app | Bootstrap's JS expects direct DOM control; React re-renders fight it | Use react-bootstrap/reactstrap (real React components), not Bootstrap's own JS bundle |
| Shipped CSS is much larger than expected | Bootstrap's full framework was imported without purging unused component styles | Set up PurgeCSS/Bootstrap's tree-shaking build, or switch to a utility framework that only emits used classes |
| Custom button variant needs its own recompile | Bootstrap's component classes are pre-compiled from Sass variables | Add a Sass override and rebuild, or move variant logic to the call site (Tailwind's model) |
| "Which do I pick for a new React app" | Comparing a JS library to a CSS framework, not two alternatives | Decide the CSS layer (Tailwind vs. Bootstrap vs. something else) — React isn't the variable here |
| jQuery appears in the bundle unexpectedly | A Bootstrap component or plugin still depends on it | Confirm with npm ls jquery; most modern Bootstrap 5 builds dropped the hard jQuery dependency, but plugins may still pull it in |
FAQ
Is React better than Bootstrap? They're not comparable directly — React builds UI, Bootstrap styles it. The real choice is which CSS approach to pair with React: Bootstrap's component classes, Tailwind's utilities, or a component library like MUI. This site picked Tailwind and measures the result: one 74 KB stylesheet for the whole storefront.
Can I use Bootstrap with React?
Yes, but not by dropping Bootstrap's own CSS-and-JS bundle into a React app unmodified — its JavaScript expects to own the DOM nodes it manages, which conflicts with React's rendering model. react-bootstrap and reactstrap solve this by reimplementing Bootstrap's components as native React components that just happen to use Bootstrap's class names.
Why does this site use Tailwind instead of Bootstrap? Two measurable reasons: no unused CSS ships (Tailwind only generates rules for classes actually written in the JSX, verified above at 1,153 rules for 428 routes), and no separate JS runtime is needed for interactive components — every one here (accordions, tabs, the mobile menu) is plain React state.
Does dropping Bootstrap mean losing pre-built components? Not automatically — it means building or buying them instead of importing a framework's bundled set. This catalog's landing page templates ship the components pre-built on Tailwind, which is the middle ground between "hand-roll every component" and "import a whole CSS framework for the ten classes you actually use."
Templates in this post
ASoc Beaker is a science-laboratory landing page built on the same Tailwind + React stack audited above — every button, card and section is a composed set of utility classes, not a framework import. ASoc Blueprint is an app-development agency page with the same component approach, and ASoc Brief is a resume/portfolio template for the same reason: buying a Tailwind-built page skips the styling decision this post walks through.
Browse the full catalog: Next.js landing page templates, Tailwind landing page templates.
