-
Notifications
You must be signed in to change notification settings - Fork 66
Description
I asked Claude about whether or not we should now completely drop our Bootstrap 3.3.6 dependency given our recent design-tokens.css refactor. Response: https://claude.ai/share/6ae8d3a1-461e-45ff-84e7-cbdd2efe2dd5
Bootstrap Migration Analysis: Makeability Lab Website
Date: January 2, 2026
Current Stack: Bootstrap 3.3.6 + jQuery 1.9.1
Recommendation: Drop Bootstrap, go fully custom with vanilla CSS/JS
Executive Summary
The Makeability Lab website has evolved to the point where Bootstrap is more burden than benefit. With design-tokens.css and modern CSS (flexbox/grid) already powering the newer components, you're essentially maintaining two styling systems. Removing Bootstrap eliminates ~200KB of overridden CSS/JS, removes jQuery security concerns, and simplifies the codebase.
Current Bootstrap Usage Analysis
Component Inventory
| Component | Usage Count | Current State |
|---|---|---|
| Grid system | ~242 instances | Mixed - newer pages use CSS Grid |
| Navbar | 1 (responsive collapse) | Works but dated |
| Carousel | 2 (hero + project pages) | Functional |
| Popover | Citations only | Already has custom JS wrapper |
| ScrollSpy | Publications TOC | Single use |
| Responsive utilities | ~30 (hidden-xs, etc.) |
Trivial to replace |
Grid Class Distribution
col-md-* : Most common (main layouts)
col-xs-* : Mobile overrides
col-sm-* : Tablet breakpoints
col-lg-* : Large screen tweaks
container : Page wrappers
row : Grid rows
JavaScript Dependencies
// Bootstrap JS components in use:
$('.carousel').carousel(); // Hero banners
$(trigger).popover('toggle'); // Citations
data-spy="scroll" // Publications TOC
data-toggle="collapse" // Mobile navbarWhy NOT Upgrade to Bootstrap 5
| Factor | Bootstrap 3 → 5 Migration | Drop Bootstrap Entirely |
|---|---|---|
| Class name changes | Extensive (col-xs-* → col-*) |
One-time replacement |
| JS rewrite | Still needed (new API) | Already have custom code |
| Bundle size | ~150KB CSS + JS | 0KB added |
| jQuery | Can drop, but new learning curve | Already removing |
| Long-term maintenance | Still external dependency | Full control |
Verdict: Migration effort is similar, but dropping Bootstrap yields better long-term outcomes.
Migration Strategy
Phase 1: Remove jQuery (Week 1)
Risk: Low | Impact: High
jQuery 1.9.1 (2013) has known security vulnerabilities. Replace with vanilla JS.
// Before
$('.carousel').carousel();
$('.navbar-toggle').on('click', function() { ... });
// After
document.querySelector('.carousel'); // CSS handles transitions
document.querySelector('.navbar-toggle').addEventListener('click', ...);Files to update:
website/templates/website/base.html(remove jQuery script tag)website/static/website/js/top-navbar.jswebsite/static/website/js/citationPopoverSimple.js
Phase 2: Replace Bootstrap JS Components (Week 1-2)
Navbar Toggle (~20 lines)
// static/website/js/navbar.js
const NavbarToggle = (function() {
'use strict';
function init() {
const toggle = document.querySelector('.navbar-toggle');
const collapse = document.querySelector('.navbar-collapse');
if (!toggle || !collapse) return;
toggle.addEventListener('click', () => {
const isExpanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', !isExpanded);
collapse.classList.toggle('in');
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!e.target.closest('.navbar') && collapse.classList.contains('in')) {
collapse.classList.remove('in');
toggle.setAttribute('aria-expanded', 'false');
}
});
}
return { init };
})();
document.addEventListener('DOMContentLoaded', NavbarToggle.init);Carousel (~60 lines)
// static/website/js/carousel.js
const Carousel = (function() {
'use strict';
function init(selector, options = {}) {
const carousel = document.querySelector(selector);
if (!carousel) return;
const slides = carousel.querySelectorAll('.carousel-item');
const indicators = carousel.querySelectorAll('.carousel-indicator');
const interval = options.interval || 10000;
let currentIndex = 0;
let timer = null;
function showSlide(index) {
slides.forEach((slide, i) => {
slide.classList.toggle('active', i === index);
});
indicators.forEach((ind, i) => {
ind.classList.toggle('active', i === index);
ind.setAttribute('aria-selected', i === index);
});
currentIndex = index;
}
function next() {
showSlide((currentIndex + 1) % slides.length);
}
function startAutoplay() {
if (slides.length > 1) {
timer = setInterval(next, interval);
}
}
function stopAutoplay() {
clearInterval(timer);
}
// Pause on hover/focus
carousel.addEventListener('mouseenter', stopAutoplay);
carousel.addEventListener('mouseleave', startAutoplay);
carousel.addEventListener('focusin', stopAutoplay);
carousel.addEventListener('focusout', startAutoplay);
// Indicator clicks
indicators.forEach((ind, i) => {
ind.addEventListener('click', () => showSlide(i));
});
startAutoplay();
}
return { init };
})();ScrollSpy with IntersectionObserver (~30 lines)
// static/website/js/scrollspy.js
const ScrollSpy = (function() {
'use strict';
function init(tocSelector, contentSelector) {
const toc = document.querySelector(tocSelector);
const headings = document.querySelectorAll(`${contentSelector} h2[id], ${contentSelector} h3[id]`);
if (!toc || !headings.length) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const link = toc.querySelector(`a[href="#${entry.target.id}"]`);
if (link) {
link.classList.toggle('is-active-link', entry.isIntersecting);
}
});
}, {
rootMargin: '-80px 0px -80% 0px'
});
headings.forEach(heading => observer.observe(heading));
}
return { init };
})();Phase 3: Replace Grid System (Week 2-3)
New Utility Classes
Add to design-tokens.css:
/* =============================================================================
LAYOUT UTILITIES (Bootstrap Grid Replacement)
============================================================================= */
/* Container */
.container {
width: 100%;
max-width: var(--container-max-width, 1200px);
margin-inline: auto;
padding-inline: var(--space-4);
}
.container-fluid {
width: 100%;
padding-inline: var(--space-4);
}
/* Flexbox Row */
.row {
display: flex;
flex-wrap: wrap;
margin-inline: calc(var(--space-4) * -0.5);
}
.row > * {
padding-inline: calc(var(--space-4) * 0.5);
}
/* Grid-based columns (simpler than Bootstrap's 12-col) */
.grid {
display: grid;
gap: var(--space-4);
}
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* Common layout patterns */
.layout-sidebar {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-6);
}
@media (min-width: 992px) {
.layout-sidebar {
grid-template-columns: 250px 1fr;
}
.layout-sidebar-right {
grid-template-columns: 1fr 300px;
}
}
.layout-8-4 {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-4);
}
@media (min-width: 992px) {
.layout-8-4 {
grid-template-columns: 2fr 1fr;
}
}
/* Responsive visibility */
.hide-mobile { display: none; }
.hide-tablet { display: block; }
.hide-desktop { display: block; }
@media (min-width: 576px) {
.hide-mobile { display: block; }
.hide-tablet { display: none; }
}
@media (min-width: 992px) {
.hide-tablet { display: block; }
.hide-desktop { display: none; }
}
/* For cases where you need the 12-column grid */
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: var(--space-4);
}
.col-span-1 { grid-column: span 1; }
.col-span-2 { grid-column: span 2; }
.col-span-3 { grid-column: span 3; }
.col-span-4 { grid-column: span 4; }
.col-span-6 { grid-column: span 6; }
.col-span-8 { grid-column: span 8; }
.col-span-12 { grid-column: span 12; }
@media (min-width: 992px) {
.md\:col-span-2 { grid-column: span 2; }
.md\:col-span-4 { grid-column: span 4; }
.md\:col-span-8 { grid-column: span 8; }
.md\:col-span-10 { grid-column: span 10; }
}Template Migration Pattern
<!-- Before (Bootstrap) -->
<div class="container">
<div class="row">
<div class="col-md-8">Main content</div>
<div class="col-md-4 hidden-xs hidden-sm">Sidebar</div>
</div>
</div>
<!-- After (Custom) -->
<div class="container">
<div class="layout-8-4">
<main>Main content</main>
<aside class="hide-mobile hide-tablet">Sidebar</aside>
</div>
</div>Phase 4: Remove Bootstrap Files (Week 3)
- Delete
static/website/css/bootstrap.css - Delete
static/website/css/bootstrap-theme.css - Delete
static/website/css/bootstrap-modifications.css - Remove Bootstrap CDN script from
base.html - Remove jQuery CDN script from
base.html - Run full visual regression test
File-by-File Migration Checklist
Templates to Update
-
website/templates/website/base.html- Remove Bootstrap/jQuery, add new JS -
website/templates/website/index.html- Carousel markup -
website/templates/website/project.html- Carousel, grid -
website/templates/website/member.html- Grid layout -
website/templates/website/publications.html- Grid, ScrollSpy -
website/templates/website/people.html- Grid (already mostly custom) -
website/templates/website/project_listing.html- Grid (already mostly custom) -
website/templates/website/news_item.html- Grid layout -
website/templates/website/news_listing.html- Grid (already custom) -
website/templates/snippets/display_pub_snippet.html- Column classes -
website/templates/snippets/display_citation_link_snippet.html- Popover
JavaScript to Update/Create
- Create
static/website/js/navbar.js - Create
static/website/js/carousel.js - Create
static/website/js/scrollspy.js - Update
static/website/js/citationPopoverSimple.js- Remove jQuery - Update
static/website/js/top-navbar.js- Remove jQuery
CSS to Update
- Extend
design-tokens.csswith layout utilities - Update
top-navbar.cssfor vanilla collapse - Update
publications.cssfor vanilla ScrollSpy states - Delete
bootstrap.css,bootstrap-theme.css,bootstrap-modifications.css
Effort Estimate
| Task | Hours | Priority |
|---|---|---|
| Vanilla navbar JS | 2 | High |
| Vanilla carousel JS | 3 | High |
| Vanilla popover (update existing) | 2 | Medium |
| Vanilla ScrollSpy | 1 | Medium |
| Layout utility CSS | 2 | High |
| Template grid migration (~15 files) | 8-12 | High |
| Testing & visual QA | 4 | High |
| Total | 22-26 hours |
Benefits Summary
| Benefit | Impact |
|---|---|
| Bundle size reduction | -200KB (Bootstrap CSS + JS + jQuery) |
| Security | Remove jQuery 1.9.1 vulnerabilities |
| Performance | Faster page loads, no unused CSS |
| Maintainability | Single styling system (design tokens) |
| Developer experience | Modern CSS/JS, no framework quirks |
| Future-proofing | No external dependency updates needed |
Risks & Mitigations
| Risk | Likelihood | Mitigation |
|---|---|---|
| Visual regressions | Medium | Screenshot comparison before/after each template |
| Edge cases in old browsers | Low | Your audience is academic/tech - modern browsers |
| Timeline slip | Medium | Migrate incrementally, keep Bootstrap until each page is done |
| Popover complexity | Low | Existing citationPopoverSimple.js handles most logic |
Recommendation
Proceed with migration. The codebase is already 70% transitioned to custom CSS. The remaining work is straightforward, and the long-term benefits (security, performance, maintainability) justify the ~25 hour investment.
Suggested Timeline
- Week 1: Remove jQuery, create vanilla JS components
- Week 2: Migrate high-traffic pages (index, publications, people)
- Week 3: Migrate remaining pages, remove Bootstrap, final QA
Appendix: Quick Reference
Bootstrap → Custom Class Mapping
| Bootstrap Class | Custom Replacement |
|---|---|
container |
container (keep, redefine) |
row |
row or grid or specific layout |
col-md-8 |
md:col-span-8 or layout class |
col-md-4 |
md:col-span-4 or layout class |
hidden-xs |
hide-mobile |
hidden-sm |
hide-tablet |
hidden-md hidden-lg |
hide-desktop |
visible-xs |
(use default + hide others) |
pull-right |
margin-left: auto or flexbox |
text-center |
text-center (keep) |
New Layout Patterns
<!-- Two-column with sidebar -->
<div class="layout-sidebar">...</div>
<!-- 8-4 split -->
<div class="layout-8-4">...</div>
<!-- Equal columns -->
<div class="grid-3">...</div>
<!-- Custom 12-col when needed -->
<div class="grid-12">
<div class="col-span-8 md:col-span-10">...</div>
<div class="col-span-4 md:col-span-2">...</div>
</div>