Ben Nolan’s Behaviour.js and the rise of unobtrusive JavaScript

Published:

Today it is taken for granted that HTML holds the content, CSS holds the presentation and JavaScript holds the behaviour, each in its own file. In the early 2000s that was not how most pages were built. One small library that helped change the habit was Behaviour, written by Ben Nolan in 2005.

The problem: JavaScript tangled into HTML

Most interactive pages of that era wired up JavaScript with inline event attributes. A link might carry onclick="toggleMenu(this); return false;", a form field might have onfocus and onblur handlers, and a page with any real interactivity ended up with script scattered through every template.

That approach had familiar problems. Markup became hard to read and harder to change. The same handler was copied into dozens of places. Pages often broke badly when JavaScript was turned off or failed to load, because links pointed to "#" and relied on the script to do anything useful. Web standards advocates were already arguing for separating structure from presentation with CSS; the next step was separating behaviour too, an idea that became known as unobtrusive JavaScript.

Ben Nolan’s idea: CSS selectors for behaviour

Behaviour’s insight was simple. Designers already had a precise, familiar way to point at elements on a page: CSS selectors. If a stylesheet can say “every link inside the navigation list”, why not use the same selector to say “every link inside the navigation list should open its submenu when clicked”?

With Behaviour you wrote a set of rules, an object whose keys were CSS selectors and whose values were functions. You registered them with Behaviour.register, and when the page loaded the library found every element matching each selector and passed it to the function, which attached event handlers such as element.onclick. The HTML stayed clean, with only ids and class names, and all of the behaviour lived in one script file.

Under the hood Behaviour built on Simon Willison’s getElementsBySelector, a script from 2003 that implemented a subset of CSS selector matching in JavaScript. Browsers at the time had nothing like querySelectorAll, so finding elements by selector meant walking the document by hand.

Because pages built with Ajax often replaced parts of the document after it loaded, Behaviour also provided Behaviour.apply, which re-ran the rules so that newly inserted elements picked up their handlers.

Why it caught on

Behaviour arrived at exactly the right moment. 2005 was the year the term Ajax was coined, Google Maps and Gmail were showing what JavaScript could do, and developers were suddenly writing far more front-end code than before. Behaviour was tiny, easy to understand and worked alongside other libraries such as Prototype. It gave people a clean pattern at the point when they most needed one.

It also made the case for unobtrusive JavaScript in a way that articles alone had not. You could look at a page built with Behaviour, see plain semantic HTML, and still get a rich interactive experience. If the script failed, links and forms could fall back to their ordinary behaviour.

The limits of the approach

Behaviour also exposed the weaknesses of doing this in 2005 browsers. Matching selectors in JavaScript was slow, so a page with many rules or a large document could take noticeably long to wire up. The rules ran on window.onload, which waits for every image to finish downloading, so there was often a window where the page was visible but not yet interactive.

Re-applying rules after every Ajax update was easy to forget, and applying them twice could attach duplicate handlers. And because it assigned handlers directly, as in element.onclick, one rule could overwrite another’s handler on the same element.

The road to jQuery

Behaviour’s biggest legacy may be the libraries it inspired. In the months before jQuery’s release in January 2006, John Resig wrote about Behaviour’s selector-based approach and argued that it could be much more concise: instead of registering a rules object, select elements with a CSS selector and chain methods on the result. That idea, selecting with CSS and then acting on the whole set, became the core of jQuery.

jQuery also fixed many of Behaviour’s rough edges. Its ready event fired when the document was parsed rather than waiting for images, it attached handlers without overwriting existing ones, and it smoothed over browser differences. Later it added event delegation, first through .live and then through .delegate and .on, so a single handler on a parent element could cover elements added long after the page loaded, which removed the need to re-apply rules at all.

Where the idea lives on

Browsers eventually built Behaviour’s core idea in. The Selectors API gave every browser native querySelector and querySelectorAll, addEventListener became universal, and DOMContentLoaded replaced waiting for onload. Today a few lines of plain JavaScript can do what Behaviour needed a library for.

The pattern itself is still around too. Libraries such as Stimulus attach behaviour to server-rendered HTML through attributes and a small controller class, and tools like htmx and Alpine.js describe interactivity declaratively in the markup. The details differ, but the goal Behaviour set out in 2005 is the same: keep the HTML meaningful, and attach behaviour to it cleanly from the outside.

Why it matters

Behaviour was only a few kilobytes of code, and most developers today have never used it. But it captured an idea at the moment the web was ready for it, showed that CSS selectors were the natural way to connect scripts to pages, and helped set the stage for jQuery and much of the front-end tooling that followed.