Chapter 9: jQuery and the Compatibility Era
For a long time, writing JavaScript for the browser meant writing JavaScript for browsers.
Plural mattered.
Chapter 6 described the browser-engine landscape of the mid-2000s. Internet Explorer 6 was dominant, frozen, and accumulating bugs. Firefox was emerging as a credible competitor. Safari had launched and was growing. Opera was independent. Each of these browsers had its own JavaScript implementation, its own DOM, its own event model, its own quirks. The W3C had begun standardizing the DOM and the events API by the late 1990s, but standards lagged adoption and adoption lagged shipped browsers. A web developer writing JavaScript in 2005 was negotiating between four or five competing implementations of the same supposed standards.
The everyday surface of this incompatibility was relentless. To attach a click handler, you wrote:
if (element.addEventListener) { element.addEventListener('click', handler, false) // Modern, Mozilla, Safari, Opera} else if (element.attachEvent) { element.attachEvent('onclick', handler) // IE 6, 7, 8}Inside the handler, the event object had different shapes:
function handler(event) { event = event || window.event // IE used window.event var target = event.target || event.srcElement // IE used srcElement // ...}To make an Ajax request, you wrote:
var xhrif (window.XMLHttpRequest) { xhr = new XMLHttpRequest() // Most browsers} else { xhr = new ActiveXObject('Microsoft.XMLHTTP') // Old IE}To get the computed style of an element, you wrote:
var style = element.currentStyle // IE || window.getComputedStyle(element, null) // Everyone elseMultiply this kind of conditional across selectors, traversal, classes, properties, animation, form serialization, and dozens of other surfaces, and you get the daily reality of a working frontend developer in 2005. The platform had real capabilities. Reaching them through the available APIs was an exercise in patience.
This is the context jQuery showed up in.
John Resig at BarCamp NYC
Section titled “John Resig at BarCamp NYC”On January 14, 2006, a 21-year-old developer named John Resig presented a small JavaScript library at BarCamp NYC, a casual conference held in the offices of CMP Media. The library was called jQuery.
Resig was, at the time, working as a freelance JavaScript developer and contributing to the Prototype.js project (which Chapter 10 will get to). jQuery wasn’t his first library — he’d been publishing experimental JavaScript code on his blog for several years — but it was the one that broke through. The 1.0 release came in August 2006, and by the end of that year the library was being adopted across the industry.
Resig’s design choices were specific enough to be worth naming.
The first was use CSS selectors. Web developers already knew CSS — they used it every day for styling — and CSS selectors were a familiar, expressive way to describe elements. jQuery’s central API was a function that took a CSS selector and returned a wrapped collection of matching elements:
$('.book')$('#cart')$('input[type="email"]')$('li:first-child a')This was significantly more ergonomic than the platform alternatives of 2006. document.getElementById was available everywhere. document.getElementsByTagName was available everywhere. document.getElementsByClassName was Mozilla-only at the time. A general CSS-selector-based query function didn’t exist in the platform — querySelector and querySelectorAll wouldn’t ship in IE until version 8 in 2009. jQuery filled this gap before the platform did.
The second design choice was chain methods. Operations on a jQuery collection returned the collection, so calls could be chained:
$('.book') .addClass('selected') .css('color', 'red') .on('click', function() { $(this).toggleClass('open') })The chained style was readable, dense, and matched the mental model of selecting a set of elements and then doing things to them. It also discouraged the kind of intermediate-variable noise that direct DOM code tended to produce.
The third choice was normalize the platform underneath. Every jQuery method that wrapped a DOM API also smoothed over its cross-browser differences. .on('click', handler) worked the same way on every browser; the library’s internal implementation chose between addEventListener and attachEvent as appropriate, normalized the event object, handled the this binding, and presented a uniform contract. The developer didn’t have to know what the underlying browser supported.
These three choices — CSS selectors, chaining, normalization — were the library’s core ergonomic argument. Each one addressed a specific friction in the platform of 2006. Together, they were enough.
Sizzle and the Selector Engine
Section titled “Sizzle and the Selector Engine”jQuery’s selector engine, internally, was sophisticated enough to be its own project.
In 2008, Resig extracted the selector engine into a standalone library called Sizzle. The extraction was deliberate. The CSS-selector-to-DOM-elements problem was sufficiently general that other libraries (MooTools, Dojo, parts of the Prototype.js ecosystem) might want to reuse it without taking the rest of jQuery as a dependency. Sizzle became, for a few years, the de facto standard JavaScript selector engine, used inside several major libraries simultaneously.
Sizzle’s existence is worth noting because it illustrates the era’s open-source dynamic. Libraries borrowed from each other liberally. The shared substrate of how do we make CSS selectors work in a JavaScript runtime that doesn’t have them natively was a common problem, solved well once, and then reused widely. The pattern recurs throughout this book’s history. Each generation of libraries solves a platform gap, and the platform eventually absorbs the solution. Sizzle became unnecessary when querySelectorAll shipped natively in every browser around 2010.
What jQuery Normalized
Section titled “What jQuery Normalized”The selector engine was the headline feature. The library also normalized a lot of other surfaces.
Events. The on and off methods presented a uniform event-binding API across all the cross-browser variations described earlier in this chapter. The event object passed to handlers was normalized — event.target, event.preventDefault, event.stopPropagation all worked predictably regardless of which browser fired the event.
Ajax. jQuery’s $.ajax was, for most of the late 2000s and early 2010s, the way most JavaScript code talked to a server. The API was simpler than XMLHttpRequest, abstracted over the cross-browser ActiveX-vs-XHR split, handled response parsing (JSON, XML, HTML), supported callback chains, and provided convenience wrappers ($.get, $.post, $.getJSON) for the most common cases. When fetch shipped in modern browsers years later, it deliberately echoed $.ajax’s shape.
Animation. $.fn.animate let developers animate CSS properties (height, opacity, position) with one line of code at a time when CSS animations didn’t yet exist in browsers and the alternative was manual setInterval loops. The animation engine was simple — a setInterval calling property updates at 60 frames per second — but it was reliable and uniform. The famous .fadeIn() / .fadeOut() / .slideDown() / .slideUp() shortcuts became iconic to the era.
CSS properties. $.fn.css smoothed over the currentStyle vs getComputedStyle split, handled property-name casing differences (backgroundColor vs background-color), and gave developers a uniform way to read and write inline styles.
Forms. .serialize() produced a query-string representation of a form’s values, which combined with $.post made Ajax form submission a two-line operation at a time when the manual version was twenty lines of XMLHttpRequest plumbing.
Each of these utilities solved a specific friction. Collectively, they made the JavaScript a developer actually wrote shorter, more readable, and more reliable across browsers than the manual equivalents would have been. For most working frontend developers between roughly 2007 and 2015, not using jQuery would’ve been a costly choice — the friction it removed was real, every day.
The Plugin Ecosystem
Section titled “The Plugin Ecosystem”jQuery’s other major contribution was its plugin model.
The library’s design made it easy to extend. A plugin was a function attached to $.fn that became available on every jQuery collection:
$.fn.tooltip = function(options) { return this.each(function() { // attach tooltip behavior to this element })}
// Now usable anywhere:$('.tooltip-target').tooltip({ position: 'top' })The model spread. By 2010, the jQuery plugin ecosystem included date pickers, carousels, modals, lightboxes, dropdowns, autocompletes, validation libraries, masonry layout systems, image galleries, charting libraries, drag-and-drop systems, sortable list utilities, and thousands of more specialized widgets. Some of these plugins were excellent. Many were not. The bar to publishing was low, and the quality varied wildly.
The most institutionally significant plugin set was jQuery UI, an officially-sponsored extension that provided a coherent visual library — themeable widgets for buttons, dialogs, datepickers, sliders, tabs, accordions, progress bars, and a suite of interaction behaviors (draggable, droppable, resizable, sortable, selectable). jQuery UI 1.0 shipped in September 2007. For most of the late 2000s and into the 2010s, it was the most widely-deployed component library in production websites.
The plugin model showed something important about what developers wanted from a frontend library. They didn’t just want primitives; they wanted attachable capabilities. The pattern of take a DOM element, install behavior on it, configure with options is the same pattern that modern web components, Lit elements, and Kitsune’s modules formalize. jQuery’s plugin model anticipated the capabilities argument by fifteen years.
What it didn’t solve was the architecture above the plugin. Plugins had their own options, events, DOM expectations, accessibility quality, styling assumptions, and lifecycle. Multiple plugins on the same page could conflict. State could end up in DOM data attributes, in plugin closures, in jQuery’s internal cache, or in hidden form fields, with no coordination between them. The page worked, but the page’s behavior was distributed across an opaque mesh of independently-attached scripts.
This is the architectural cost the chapter has to land. jQuery made DOM programming easier. It didn’t solve application architecture, and most applications past a certain size eventually needed something the plugin model couldn’t provide.
DOM-Centric Programming
Section titled “DOM-Centric Programming”The deeper style jQuery encouraged was DOM-centric. The HTML existed first. JavaScript reached into it.
A typical jQuery application architecture, if such a thing existed, looked like this. The server rendered the HTML. The page loaded. A $(document).ready(...) block ran on load. The ready block selected elements, attached behavior, and initialized plugins. The page was now interactive.
This worked well for what it was designed for. A server-rendered application that needed to be made more interactive — a content site, an e-commerce store, a CMS-driven product — could be enhanced with jQuery incrementally without restructuring its backend. Developers could add behavior to one page, then another, then a third, without committing to a full client-side architecture.
The style ran into limits when the application stopped being a server-rendered page that needed enhancement and became something more like a client-side application that needed to manage its own state. The DOM-centric style had no answer for the user’s intent is X, the application state is Y, the UI should display Z. It had answers for find the element that represents Z, attach behavior to it, update it when something happens. Those answers are powerful for some kinds of applications and inadequate for others.
The single-page application generation that followed jQuery — Backbone in 2010, then Angular, Ember, Knockout, eventually React — was built around the answer jQuery didn’t have. UI as a function of state, with the JavaScript producing the DOM rather than reaching into it, was the architectural alternative that won when applications got complicated enough.
This wasn’t a defeat for jQuery. It was a clarification. jQuery did one thing extraordinarily well: it made server-rendered HTML easier to enhance with JavaScript. When that wasn’t the right architecture for the product, jQuery had to share the page with other tools, and eventually got displaced from new projects entirely.
The Slow Absorption
Section titled “The Slow Absorption”The platform caught up.
document.querySelector and document.querySelectorAll, the W3C-standardized CSS selector APIs, shipped in IE8 in 2009 and reached general support shortly after. The need for Sizzle disappeared.
classList, the standardized API for managing CSS classes on an element, shipped in 2012 and reached general support around 2014. The need for $.fn.addClass, removeClass, and toggleClass disappeared.
addEventListener had been in non-IE browsers since 2000, and IE finally adopted it — replacing attachEvent — in IE9 in 2011. The need for event normalization disappeared.
fetch shipped in Chrome and Firefox in 2015 and reached general browser support by 2017. The need for $.ajax disappeared.
CSS transitions and CSS animations matured around 2012–2014, and the Web Animations API followed later. The need for $.fn.animate disappeared.
Each of these absorptions removed one of jQuery’s reasons for existing. By around 2017, almost every original justification for the library was a native part of the platform. The phrase you might not need jQuery — coined as a website and a viral discussion topic around 2014 — captured the shift exactly. You probably didn’t.
This is the cleanest illustration the book has of the library-on-platform pattern. jQuery solved a real problem of its day. The platform absorbed the solution. The library became optional. The platform’s evergreen guarantee — described in Chapters 5 and 6 — is what made the absorption possible without breaking the millions of pages still depending on jQuery.
Still Everywhere
Section titled “Still Everywhere”Here’s the strange epilogue. jQuery is still installed on a substantial fraction of the web.
In 2024, statistics from W3Techs put jQuery on roughly 75% of websites with a detectable JavaScript library. That’s not a measurement error. WordPress, which powers about 43% of the public web by content-management-system share, includes jQuery in its core distribution and has done so since 2006. The vast majority of WordPress themes and plugins depend on jQuery. Drupal does the same. Most enterprise CMS-driven sites — Adobe Experience Manager, Sitecore, older Salesforce Commerce Cloud installations — bundle jQuery. The financial-services and government sectors are particularly heavy jQuery users.
jQuery 3.x is actively maintained by a small team and gets regular releases. The 3.7.x line is current as of 2025. The library is no longer growing; it’s no longer changing much. It’s settling into the role of a stable, reliable piece of infrastructure that the web still needs because the web’s existing pages still depend on it.
This is the durable form of library that earned its keep. jQuery’s modern existence isn’t a frontier; it’s foundation. It runs on millions of pages. It breaks roughly never. The maintainers ship security updates and small fixes. The library that defined the frontier in 2007 has become the substrate the frontier has moved past — and it’s still there, holding up the part of the web that hasn’t moved with it.
What jQuery Taught the Field
Section titled “What jQuery Taught the Field”A few lessons from the jQuery era that the rest of this book carries forward.
Ergonomics matter. Developers adopt tools that make common work easier. The jQuery API was widely loved because it made the dull parts of DOM programming shorter and the cross-browser parts disappear. The same principle drives every successful library in this field’s history.
Compatibility matters. A library that creates a reliable baseline across an inconsistent platform can unlock an ecosystem. jQuery’s normalization layer was what made the late-2000s web possible to build for at scale. The supply-chain chapter (Ch 17) is, in part, the inverse story — what happens when the number of libraries in the dependency graph exceeds the bar at which any reasonable team can verify them all.
Incremental enhancement matters. A library that can be added to one page without restructuring everything around it is much more likely to be adopted. jQuery’s biggest design strength was that it required no commitment. You added a <script> tag and used what you needed. The platform-first argument the rest of this book builds preserves this property — code written against the platform can be added incrementally to any page without rewriting the application.
The plugin pattern was important. Attachable behavior, applied to DOM elements, configured through options — this is the shape of every component library that has come since. The pattern’s weakness in the jQuery era was the absence of an architecture above it. The pattern’s strength was that the architecture could be built later, and was.
And, finally, the platform absorbs solutions. The historical pattern is consistent enough to be a working principle. Solve a problem in a library, do it well, document it, and eventually the platform will give you a native version. The platform’s job, slow as it is, is to absorb the patterns the ecosystem proves valuable. jQuery is the canonical case.
What Comes Next
Section titled “What Comes Next”This chapter has been about the library that defined a decade of frontend development. The next chapter is about the libraries that ran alongside it — Prototype, MooTools, and Dojo — and the parallel attempts to bring application discipline to JavaScript that jQuery’s more ergonomic, less ambitious approach mostly overshadowed. Each of those libraries was solving real problems too. Some of their architectural ambitions are the threads that eventually became the MVC framework era.
Exercise: Modern DOM vs jQuery-Style Thinking
Section titled “Exercise: Modern DOM vs jQuery-Style Thinking”Build a small searchable list using modern DOM APIs.
The requirements: a search input, a list of items, filtering as the user types, a selected-item state, and a details panel that shows information about whichever item is currently selected.
First, implement it directly with modern DOM APIs:
const input = document.querySelector('[data-search]')const items = Array.from(document.querySelectorAll('[data-item]'))
input.addEventListener('input', () => { const query = input.value.toLowerCase() for (const item of items) { const text = item.textContent.toLowerCase() item.hidden = !text.includes(query) }})Then rewrite it in a jQuery-style if you have jQuery available, or with a small selector helper if you don’t. The point is to feel the difference, not to declare a winner.
Reflection:
- What did selector-driven programming make easy?
- Where did state live in each version — in the DOM, in JavaScript variables, in something else?
- Which class names were styling hooks? Which were behavior hooks? Could you tell from looking at the HTML?
- If you wanted to add analytics, audit logging, or a second selection mode, where would that code go in each version?
- How would the code change if the same list needed to appear three times on the same page, each independently?
The point isn’t to mock jQuery. The point is to see the difference between DOM convenience and application architecture, and to recognize which problems each one was built to solve.