Skip to main content
ASoc
Comparison

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.

The ASoc Team10 min read

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 unitUtility classes on the elementPre-defined component classes (.btn, .card, .navbar)
Customizing a variantAdd/change utility classes at the call siteOverride 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 CSSNone — only classes actually used in JSX are emittedEvery 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-between is 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

SymptomCauseFix
Bootstrap's dropdown/modal JS does nothing in a React appBootstrap's JS expects direct DOM control; React re-renders fight itUse react-bootstrap/reactstrap (real React components), not Bootstrap's own JS bundle
Shipped CSS is much larger than expectedBootstrap's full framework was imported without purging unused component stylesSet up PurgeCSS/Bootstrap's tree-shaking build, or switch to a utility framework that only emits used classes
Custom button variant needs its own recompileBootstrap's component classes are pre-compiled from Sass variablesAdd 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 alternativesDecide the CSS layer (Tailwind vs. Bootstrap vs. something else) — React isn't the variable here
jQuery appears in the bundle unexpectedlyA Bootstrap component or plugin still depends on itConfirm 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.

Keep reading

Comparison7 min read

Sass vs. Tailwind: 133 Lines and One Arbitrary Selector

Sass's four features, checked one at a time against this codebase's real @theme block, group-hover usage, and the single [&_selector] Tailwind still reaches for.

Read more
Comparison8 min read

shadcn vs. Tailwind Is a Category Error (One Runs on the Other)

92 components, 13 runtime dependencies, zero UI libraries — what hand-rolling actually cost this codebase, and the seven components shadcn would have handed over.

Read more