BOOTSTRAP: A ROAST IN SEVERAL ACTS
"Unfuck the Fucked: A History"
Oh, we're doing THIS today. We're roasting Bootstrap. The CSS framework that taught an entire generation of developers that "design" means "rounded corners and blue buttons."
25,387 commits. 1,685 contributors. 14 years of development.
And the first thing I find in the git history?
commit a27952efd0f25e99030a9384f66bcd602d6a1b37
Author: Mark Otto <otto@github.com>
Date: Wed Dec 19 23:12:38 2012 -0800
Unfuck the fucked gradient with it's fucked mix() of background
colors; nuke commented out code
That's right. The co-creator of Bootstrap, writing production code at 11 PM, describing his own work as "the fucked gradient with it's fucked mix()".
Note the grammatical error too. "it's" instead of "its". At 11:12 PM. We've all been there, Mark. We've all been there.
But this is just the OPENER. The archaeological dig has only just begun.
BIT #1: THE COMMIT MESSAGE HALL OF SHAME
"Communication is Hard When You're Angry"
Let's take a journey through Bootstrap's git history, shall we?
THE PROFANITY COLLECTION:
- 1e7547357 unfuck that
- 3cf763c3a unfuck margin
- 6bf63cbe6 hack the headings for sticky header so shit doesn't overlap
- fda92d02a Unfuck those split button dropdowns
- ac5be12f2 unfuck that border from rem to px
- f712e922d unfuck that class name change
- 7fcc6c788 unfuck those dropdowns
- a2c5febdb unfuck jekyll
- 58f03754c unfuck the gruntfile
- cf40b5db6 unfucks disabled fieldset form example
- 5b2d93352 Fixes #8350: unfuck Chrome number input element cursor
- ed7fe0e5b unfuck misaligned main container
- a932476f8 Unfuck the fucked homepage
COUNT: At least 13 commits with "unfuck" in the message. That's not debugging. That's therapy.
THE "PROFESSIONAL" COLLECTION:
- 7c4d17755 delete that shit
- cb030314c hella migration updates
- 6b318ef17 Hella vars for custom checkboxes and radios and selects
- b68acc893 redo hella docs scss
- 314161cb1 remove hella vars from the jumbotron
- 0a92efce0 Nuke hella things
- 024e320e5 hella examples fixing
- 94df8090a fixes for hella broken links
8 commits with "hella" in them. This is Silicon Valley slang pollution at its finest.
THE EXISTENTIAL CRISIS COLLECTION:
- 5949b8e14 stuff
- 7915584fa lol
- cc5a8a903 fix
- b17e3b174 Fix
- 63daac2fe fix
- 1ed86c905 Fix
- 1f28c7684 test
- 025fd1add update
- dd5ef0788 update
When Mark Otto committed "stuff" on June 28, 2011, he was modifying the LICENSE FILE. The message for a LICENSE change was "stuff".
And "lol"? That was committed July 29, 2024. THIRTEEN YEARS into the project, and the lead maintainer is still committing with "lol".
This is what 25,000 commits does to a person.
BIT #2: THE iOS SAFARI NOOP DISASTER
"Sometimes the Best Code Does Nothing"
I present to you js/src/dropdown.js:141:
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement &&
!this._parent.closest(SELECTOR_NAVBAR_NAV)) {
for (const element of [].concat(...document.body.children)) {
EventHandler.on(element, 'mouseover', noop)
}
}
Read that again. They are:
- Looping through EVERY CHILD of document.body
- Adding a 'mouseover' event listener to each one
- That listener does LITERALLY NOTHING (noop = () => {})
Why? Because in 2014, iOS Safari had broken event delegation.
The article they link to is from FEBRUARY 2014. It's now 2025. That's 11 YEARS of adding empty event handlers to every element on your page because Apple couldn't ship a proper browser.
And it's not just dropdowns! The same pattern appears in tooltip.js:
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
They copy-pasted this monstrosity.
The noop function itself lives in util/index.js:165:
const noop = () => {}
An empty arrow function. Exported. Imported into multiple files. Used specifically to do nothing. This is the most Bootstrap thing I've ever seen.
BIT #3: THE "GNARLY iOS SAFARI BUG"
"When Engineers Give Up on Professional Language"
scss/_modal.scss:44-49
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695
"gnarly"
This is production code. In the most popular CSS framework in the world. And they're describing Apple's browser bugs as "gnarly".
git blame shows this was added by Chris Rebert in 2016:
commit 1ca6c9d7f1d15017c549dd1375ed98bf64404b33
Author: Chris Rebert <github@chrisrebert.com>
Date: Mon Oct 3 21:36:46 2016 -0700
Remove `-webkit-overflow-scrolling: touch` due to an iOS Safari bug
See https://bugs.webkit.org/show_bug.cgi?id=158342
Fixes #17695
Chris had to remove a CSS property because iOS Safari was broken. His frustration was so palpable that he felt the need to editorialize in the codebase.
The WebKit bug? Filed 2016. The Bootstrap comment calling it "gnarly"? Still there in 2025.
The web development experience, everyone.
BIT #4: THE 952 VARIABLES OF DOOM
"Customizable to the Point of Insanity"
scss/_variables.scss - Let's talk about this file.
Lines: 1,753
Variables with !default: 952
NINE HUNDRED AND FIFTY TWO variables.
Here's how color definitions work in Bootstrap:
scss/_variables.scss:1
$blue: #0d6efd !default;
$blue-100: tint-color($blue, 80%) !default;
$blue-200: tint-color($blue, 60%) !default;
$blue-300: tint-color($blue, 40%) !default;
$blue-400: tint-color($blue, 20%) !default;
$blue-500: $blue !default;
$blue-600: shade-color($blue, 20%) !default;
$blue-700: shade-color($blue, 40%) !default;
$blue-800: shade-color($blue, 60%) !default;
$blue-900: shade-color($blue, 80%) !default;
That's 10 variables for ONE COLOR. And they have this for blue, indigo, purple, pink, red, orange, yellow, green, teal, cyan...
That's 100+ variables just for color shades. Before you even get to the 30+ gray variables. Before the theme colors. Before the semantic colors. Before the component colors.
And then there's this beauty at line 392:
scss/_variables.scss:392
$variable-prefix: bs- !default; // Deprecated in v5.2.0 for the shorter `$prefix`
They deprecated a variable because the NAME was too long.
The variable that defines the prefix for other variables was renamed. Not because it was wrong. Because it had too many characters.
At line 1753, the final line of this horror:
scss/_variables.scss:1753
@import "variables-dark"; // TODO: can be removed safely in v6, only here to avoid breaking changes in v5.3
A TODO that's been sitting there waiting for v6. Which, based on the number of "TODO: v6" comments in this codebase, is going to be the largest cleanup operation in CSS framework history.
BIT #5: THE "TODO: v6" GRAVEYARD
"Technical Debt: A Promise Deferred"
Let's grep for the technical debt Bootstrap has been putting off:
// TODO: tooltip passes `false` instead of selector, so we need to check
// TODO: this is now unused; remove later along with prev()
// TODO:v6 remove boolean support
// FIXME TODO use `document.visibilityState`
// TODO: change tests that use empty divs to avoid this check
// TODO: v6 revert #37011 & change markup
// TODO: v6 remove this or make it optional
// TODO: remove this check in v6
// TODO: v6 the following can be achieved with CSS only
// TODO: v6 @deprecated, keep it for backwards compatibility reasons
// TODO v6 @deprecated, keep it for backwards compatibility reasons
// TODO: on v6 target should be given explicitly
// TODO: v6 Only for backwards compatibility reasons. Use rootMargin only
// TODO: could only be `tab` in v6
// TODO: should throw exception in v6
That's from ONE FILE (dropdown.js) and its siblings. They appear in the dist files THREE TIMES because Bootstrap ships:
- bootstrap.js
- bootstrap.bundle.js
- bootstrap.esm.js
Each with the same TODOs.
My favorite is this one from dropdown.js:99:
// TODO: v6 revert #37011 & change markup
They made a change. It was wrong. They KNOW it was wrong. They even reference the PR number. But instead of reverting it, they left a TODO comment referencing the PR they need to undo.
PR #37011 was merged in... let me check... and now they're just WAITING for v6 to fix it.
This is what happens when backwards compatibility becomes religion.
BIT #6: THE BROWSER COMPATIBILITY MUSEUM
"Every Line a Battle Scar"
scss/_reboot.scss is essentially a war memorial. Let's count the bodies:
LINE 45-46:
// 3. Prevent adjustments of font size after orientation changes in iOS.
// 4. Change the default tap highlight to be completely transparent in iOS.
LINE 66:
// 1. Reset Firefox's gray color
LINE 137:
// 1. Add the correct text decoration in Chrome, Edge, Opera, and Safari.
LINE 199:
// Add the correct font weight in Chrome, Edge, and Safari
LINE 359:
// 3. Fix alignment for Safari
LINE 404:
// 1. Remove the margin in Firefox and Safari
LINE 417:
// Remove the inheritance of text transform in Firefox
LINE 430:
// Remove the inheritance of word-wrap in Safari.
LINE 449:
// 2. Correct the inability to style clickable types in iOS and Safari.
LINE 465:
// Remove inner border and padding from Firefox
LINE 528-532:
// 1. This overrides the extra rounded corners on search inputs in iOS
// 2. Correct the outline style in Safari.
LINE 559:
// Remove the inner padding in Chrome and Safari on macOS.
LINE 573:
// 2. Correct the inability to style clickable types in iOS and Safari.
LINE 604:
// Add the correct vertical alignment in Chrome, Firefox, and Opera.
This file is 617 lines of CSS, and roughly 20% of it exists solely to fix browser inconsistencies. Not to style anything. Just to make browsers behave like browsers should have behaved in the first place.
Welcome to web development.
BIT #7: THE LEGEND OF "FAT"
"When Your GitHub Handle Ages Poorly"
$ git blame js/src/dropdown.js | head -10
bbb97a8660 (fat 2015-05-10 13:47:11 -0700 1) /**
bbb97a8660 (fat 2015-05-10 13:47:11 -0700 2) * --------------------------------------------------
bbb97a8660 (fat 2015-05-10 13:47:11 -0700 5) * --------------------------------------------------
bbb97a8660 (fat 2015-05-10 13:47:11 -0700 6) */
The co-creator of Bootstrap chose the GitHub username "fat".
278 commits across the Bootstrap codebase. All attributed to "fat".
$ git log --all --oneline --author="fat" | head -5
a3c907794 update index.html
b12d4bfa9 fix dist css
dbaffd43c Merge pull request #116
6b2b0ed32 al tests passing, dist rebuilt, w/typechecker
eaab1def7 add simple type checker implementation
His real name is Jacob Thornton. He has 854 commits under that name.
But the truly chaotic energy commits? Those are from "fat".
This is the person who wrote:
bbb97a866 add dropdown
That's the birth of the Bootstrap dropdown. May 10, 2015. Authored by
someone who chose to be known professionally as "fat".
Every time you use a Bootstrap dropdown, you're using code originally
committed by "fat". Every enterprise application. Every government
website. Every bank portal.
The internet was built on the commits of "fat".
BIT #8: DEPRECATED BUT NEVER REMOVED
"Backwards Compatibility Über Alles"
From js/src/toast.js:
const CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for
// backwards compatibility
This class name has been deprecated. But it's still there. Still being added and removed from elements. Still shipped in every Bootstrap bundle.
Why? "Backwards compatibility."
From scss/_variables.scss:
$variable-prefix: bs- !default; // Deprecated in v5.2.0
$border-radius-2xl: $border-radius-xxl !default; // Deprecated in v5.3.0
$text-muted: var(--#{$prefix}secondary-color) !default; // Deprecated in 5.3.0
$hr-bg-color: null !default; // Deprecated in v5.2.0
$hr-height: null !default; // Deprecated in v5.2.0
$nested-kbd-font-weight: null !default; // Deprecated in v5.2.0, removing in v6
$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default;
// Deprecated in v5.3.4
Notice the pattern? "Deprecated in vX.X.X" but still present.
The one that kills me is $nested-kbd-font-weight. It's been deprecated since v5.2.0. Its value is null. It does NOTHING. But it's still there because someone, somewhere, might have overridden it in their Sass.
This is the Bootstrap social contract: "We will never remove anything. We will only add @deprecated comments and pray you notice before v6."
BIT #9: THE JQUERY GHOST
"It's 2025 and We're Still Checking for jQuery"
js/src/util/index.js:179
const getjQuery = () => {
if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
return window.jQuery
}
return null
}
Bootstrap v5 famously "dropped jQuery as a dependency." But did it?
They still CHECK for jQuery. On every page load. They still have a data attribute specifically to OPT OUT of jQuery integration.
data-bs-no-jquery
You have to explicitly tell Bootstrap "no, I really don't want jQuery" by adding an attribute to your body tag. In 2025.
And then there's defineJQueryPlugin() at lines 208-223:
js/src/util/index.js:208
const defineJQueryPlugin = plugin => {
onDOMContentLoaded(() => {
const $ = getjQuery()
/* istanbul ignore if */
if ($) {
const name = plugin.NAME
const JQUERY_NO_CONFLICT = $.fn[name]
$.fn[name] = plugin.jQueryInterface
$.fn[name].Constructor = plugin
$.fn[name].noConflict = () => {
$.fn[name] = JQUERY_NO_CONFLICT
return plugin.jQueryInterface
}
}
})
}
Every Bootstrap plugin automatically registers itself as a jQuery plugin if jQuery exists. Whether you want it to or not.
They even implemented noConflict(). FOR A LIBRARY THEY CLAIM TO NOT DEPEND ON.
jQuery is the ex Bootstrap keeps checking up on.
BIT #10: THE SACRED REFLOW
"Accessing Properties for Side Effects"
js/src/util/index.js:167-177
/**
* Trick to restart an element's animation
*
* @param {HTMLElement} element
* @return void
*
* @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-
* with-javascript/#restarting-a-css-animation
*/
const reflow = element => {
element.offsetHeight // eslint-disable-line no-unused-expressions
}
Read that function body again.
element.offsetHeight
That's it. That's the whole function. They ACCESS a property and THROW AWAY THE RESULT. Just to trigger a browser reflow.
They even had to add an ESLint disable comment because the linter CORRECTLY identified this as a no-op expression.
But wait, there's more! They EXPORT this function:
export {
...
reflow,
...
}
And it gets IMPORTED and USED across the codebase. There are developers out there calling reflow(element) without knowing they're just reading offsetHeight and discarding it.
This is the web platform equivalent of knocking on wood.
BIT #11: THE 2,436 LINE TEST FILE
"When Your Tests Need Tests"
js/tests/unit/dropdown.spec.js
2,436 lines
Two thousand four hundred and thirty-six lines of test code.
For a DROPDOWN. A thing that shows and hides a menu.
The total test file line counts:
- 672 lines - toast.spec.js
- 914 lines - offcanvas.spec.js
- 980 lines - scrollspy.spec.js
- 1,062 lines - collapse.spec.js
- 1,252 lines - tab.spec.js
- 1,329 lines - modal.spec.js
- 1,572 lines - carousel.spec.js
- 1,585 lines - tooltip.spec.js
- 2,436 lines - dropdown.spec.js
- ------
- 12,983 total lines of test code
The dropdown has MORE test code than any other component.
Because dropdowns are apparently the Higgs boson of UI components.
From line 62-83 of dropdown.spec.js:
it('should work on invalid markup', () => {
return new Promise(resolve => {
// TODO: REMOVE in v6
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Link</a>',
' </div>',
'</div>'
].join('')
const dropdownElem = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(dropdownElem)
...
expect().nothing()
})
They have a test that checks if Bootstrap works ON INVALID MARKUP.
With a TODO to remove it in v6.
They're TESTING that broken HTML still works. On purpose.
BIT #12: THE TWITTER ORIGINS
"From Internal Tool to Global Domination"
The oldest commits tell a story:
eb81782cd Porting over all Blueprint styles to new Baseline repo
677b5554f Remove the unnecessary global.js file
b9d6acf76 More documentation and content changes
0824ed4e3 Updated documentation; added stacked forms
eb3389303 Added in mini layout docs
e89ba2938 Added compiled CSS file and fixed link to do it
d61317d7b Added main topbar and shit.
bb8ebe231 More subtle grid pattern, adding dropdown arrow
Date: 2011-04-27
That's right. "Added main topbar and shit." April 27, 2011. One of the first 20 commits ever made to Bootstrap.
And this gem from the early days:
a6d5f6743 Changing up boostrap for better grid variable management
They misspelled their own framework name. "boostrap".
The git remotes tell the full story:
1561b20f7 Merge branch 'master' of http://git.local.twitter.com/bootstrap
This was originally http://git.local.twitter.com/bootstrap. An internal Twitter repository. Before it was called Bootstrap, it was called "Baseline" - you can see the old repo URLs:
a92aeda6a Merge branch 'master' of http://git.local.twitter.com/baseline
Bootstrap started as Twitter's internal CSS framework. The same company that brought us the fail whale. The same company that couldn't figure out how to edit tweets for 15 years.
And we entrusted the foundations of web design to them.
THE CLOSER: A FINAL MEDITATION
"On Bootstrap, Legacy, and the Websites We Made Along the Way"
Let's take stock of what we've found in this codebase:
- 25,387 commits over 14 years
- 1,685 contributors
- 952 Sass variables, each with !default
- 13+ commits containing "unfuck"
- 8+ commits containing "hella"
- Multiple commits with just "stuff", "lol", or "fix"
- A co-creator who went by "fat"
- Empty event listeners added to every body child element
- A "gnarly iOS Safari bug" comment still in production
- jQuery compatibility code in a framework that "dropped jQuery"
- A function that reads offsetHeight and discards it
- A 2,436 line test file for dropdowns
- Dozens of "TODO: v6" comments that have been waiting for years
And yet.
AND YET.
Bootstrap powers millions of websites. It democratized web design.
It gave backend developers the ability to create passable interfaces.
It standardized UI patterns across the industry.
Every janky workaround, every profane commit message, every deprecated
variable that was never removed - they're all battle scars from the
war to make the web actually work.
When Mark Otto committed "Unfuck the fucked gradient with it's fucked
mix() of background colors" at 11:12 PM in 2012, he wasn't just fixing
a bug. He was participating in the grand tradition of web development:
wrestling with browsers until they submit.
Bootstrap isn't beautiful code. It's WORKING code. Code that works
on Safari. Code that works on Firefox. Code that works with jQuery
and without jQuery. Code that works on invalid markup because users
write invalid markup and you can't just NOT work.
So yes, we roast Bootstrap. We roast its commit messages and its
ancient browser hacks and its 952 variables.
But we do it with respect. Because every website that looks vaguely
professional but was built by someone who "doesn't really do frontend"?
That's Bootstrap's legacy.
Every startup MVP that shipped on time because they didn't have to
write CSS from scratch? That's Bootstrap.
Every developer who learned that col-md-6 means "half the screen on
medium and up"? That's Bootstrap.
The code is messy. The history is chaotic. The workarounds are insane.
But it WORKS.
And in web development, that's all that matters.