Skip to main content Accessibility Feedback

Lean Web Principles

Let’s look at a new set of best practices—Lean Web Principles—that we can use to build a simpler, faster web.

I want to encourage you to, in a way, become a developer dinosaur.

We are, as an industry, obsessed with shiny new things. New techniques, new tools, new trends. It’s what makes this profession so exciting, but it’s what causes that feeling that you can’t keep up.

Old doesn’t mean obsolete

Old techniques don’t become invalid just because new ones come out. Often, the older approaches are simpler and more reliable than the new ones.

For example, bicycles are still a good transportation option, even though cars still exist. They’re easier to maintain. They don’t need gas. They’re quieter. You don’t need as much training to use one. They can go places cars can’t.

My mountain bike is 20 years old. I fill the tires up with air when they’re low and oil the chain every few years, and that’s it. It still works.

The same is true for development tools and techniques.

That doesn’t mean you should never use new tools and approaches. But I want to encourage you to be more selective about what you use, and why.

Does the utility outweigh the cost of using it—both for you and your users? Lean on old-and-trusted approaches, but augment them with new tools and techniques when it’s beneficial to you and your users.

Principle 1: Embrace the Platform

Rather than using dependencies or libraries, use the native JavaScript methods and Browser APIs that are baked right in for free whenever you can.

Stuff that used to be really hard, like getting elements in the DOM and manipulating classes, is really easy now.

The first line is some jQuery. The second is the equivalent in vanilla JS.

// jQuery
$('#app').addClass('awesome');

// Vanilla JS
document.querySelector('#app').classList.add('awesome');

Here’s some JSX code I copy/pasted from the React documentation website…

// JSX
var name = 'Josh Perez';
var element = <h1>Hello, {name}</h1>;

ReactDOM.render(
    element,
    document.getElementById('root')
);

… and the same thing in vanilla JS.

// Vanilla JS
var name = 'Josh Perez';
var element = `<h1>Hello, ${name}</h1>`;

document.getElementById('root').innerHTML = element;

Use HTML instead of JavaScript

Sometimes, the platform provides HTML elements to handle stuff that would otherwise require JavaScript.

For example, the details and summary elements create accordion components.1 They’re accessible, can be styled, and even emit an event you can hook into with JavaScript if you want to extend them a bit.

<details>
	<summary>The toggle</summary>
	The content.
</details>

You can get a native HTML autocomplete component2 using a humble input and associating it with a datalist element.

<label for="wizards">Who's the best wizard?</label>
<input type="text" id="wizards" name="wizards" list="wizards-list">
<datalist id="wizards-list">
	<option>Harry Potter</option>
	<option>Hermione</option>
	<option>Dumbledore</option>
	<option>Merlin</option>
	<option>Gandalf</option>
</datalist>

Use CSS instead of JavaScript

Similarly, CSS can also handle a lot of stuff that you would historically use JS for—animations, in particular.

Daniel Eden has built an entire library of CSS animations3 you can poach from.

My most popular JS plugin—the hardest one I’ve ever written—is a vanilla JS smooth scroll animation plugin.4 It animates scrolling to anchor links on a page.

A one-line CSS property now does the same thing: scroll-behavior: smooth.5

html {
	scroll-behavior: smooth;
}

@media screen and (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}
}

(For accessibility reasons, you should disable this behavior when prefers-reduce-motion is activated.)

Multi-Page Apps

Routing is something that browser’s handle for you already out-of-the-box. Single Page JS Apps break it, so we add even more JavaScript to put it back in.


The argument in favor of SPAs is that they’re a lot more performant because you avoid expensive page reloads. But there’s another way to get fast page loads and remove that complexity.

Instead of a single page app, you can have a multipage app.

Each page renders a different part of your app, and you let the browser handle the routing for you. You can still render content with JavaScript if you want. You just do it across multiple HTML files instead of one.

That doesn’t mean you have to render all of your markup server-side or use database driven websites. In fact, it’s probably better if you don’t.

Static HTML

When someone visits a webpage for a database-driven site, the server pulls content from the database, figures out which templates to use, and then combines the two into an HTML file that it sends back.

This takes time, and depending on your server, it can take a lot of time.

With static HTML files, the response is nearly instant. The browser requests a file and gets one back. Couple this with sending less code down the wire in the first place, and you get near instant page loads.

I use this approach for the portal my students use6 to access their ebooks and courses.

I serve up the “won’t ever change” markup (navigation, titles, and such) with static HTML files. The main body content is specific to the user and what they’ve purchased. That comes from an API call and is rendered with vanilla JS.

Page loads feel instant, even though each page is a completely new HTML document.

Static Site Generators

If your site or app is just a few pages, creating static HTML files by hand isn’t that difficult. But if you’re managing a larger project, it would be impractical to code all those pages by hand.

Fortunately, Static Site Generators give you the convenience of database-driven sites, simpler templating, and the huge performance wins of static HTML files.

Tools like Hugo,7 Eleventy,8 and Jekyll9 let you more easily create many HTML files by combining templates and data together.

But they do it ahead of time, before a user ever visits your site.

What about persistent data?

The question I always get around this is:

What about persistent data that should carry across multiple views?

I use sessionStorage or localStorage for that.

var saveData = function (data) {
    sessionStorage.setItem(
        'myData',
        JSON.stringify(data)
    );
};

var getData = function () {
    var saved = sessionStorage.getItem('myData');
    if (saved) {
        return JSON.parse(saved);
    }
    return {};
};

Principle 2: Small & Modular

It’s common practice in our industry to reach for multi-purpose tools. “Look at all the utilities that are baked in!” Features are good.

We go for the multi-purpose utility knife when the thing we really need is scissors.

Perhaps we should instead be reaching for small, focused tools that do just one thing well (and at that, only when the platform alone won’t work).

Framework Alternatives

Instead of including 27kb of lodash just to use one or two functions, like _.groupBy(), you can include a 0.15kb (150 bytes) helper function10 that does the same thing.

Instead of loading the entire jQuery library (and jQuery UI) to add a toggle tabs plugin, you can load one built on top of native browser methods and APIs11 instead.

Instead of loading 30kb+ of Angular, or React, or Vue, you can use hyperHTML,12 which is just less than a kilobyte after minifying and gzipping. Or Preact,13 a 3kb alternative to Reac with the same modern API.

Or Reef,14 which is just 2.5kb. Or Svelte15, or Preact,16 or lit-html.17

Object-Oriented CSS

Earlier, we talked about CSS-in-JS.

It helps prevent redundancy in your stylesheet by creating separate classes for each of the individual properties you set for a particular component.

This is a great approach, but you don’t need a JS tool to do that.

Object-Oriented CSS (or OOCSS) was created by Nicole Sullivan18 quite a few years ago.

If you’re familiar with things like Atomic CSS or utility-first CSS, they’re derivative of Nicole’s work (though she seldom gets the credit she deserves here).

 OOCSS treats CSS classes like legos for FE devs.

Looking at the same component again, instead of styling the component directly, you would create utility classes for what you’re trying to accomplish. This is effectively the same result as the CSS-in-JS example, but with classes that you can read an understand.

.bg-gray {
    background-color: slategray;
};

.text-large {
    font-size: 2em;
}
<div class="bg-gray text-large">
    👋 Hi there!
</div>

Initially, this seems to result in more CSS to accomplish the same thing. And for any one component, that may be true.

But once you get a small collection of these in a project, you can start to mix-and-match them across components to get the look and feel you want without adding more CSS to your code base.

It also helps drive more consistency across the UI, reducing the chances of each component have slight variances in things like color, typographic scale, and so on.

Principle 3: The Web is for Everyone

I advocate for using what the platform gives you whenever you can. But those native features aren’t available on every device or browser.

One of the big advantages of libraries and frameworks used to be that they standardized behavior across browsers.

Use Polyfills

You can also do that with polyfills: little snippets of code that add support for features to browsers that don’t natively support them.

Here’s a polyfill for the Array.forEach() method.

if (!Array.prototype.forEach) {
  Array.prototype.forEach =
    function (callback, thisArg) {
      thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}

It checks to see if the feature already exists in the browser. If not, it uses some code supported by older browsers to replicate the the native feature. In this case, it’s using an old-school for loop.

Polyfill.io,19 from the team at the Financial Times, is a free service that makes using polyfills super easy.

<script src="https://polyfill.io/v3/polyfill.min.js"></script>

Include it on your site just like you would any other JS file.

It automatically detects the user’s browser and it sends back only the polyfills they need. 0kb on the latest Chrome, and ~17kb min and gzipped on IE8.

Build in Layers

Progressive Enhancement has fallen a bit out of favor lately.

There’s this line of thinking that says people shouldn’t be turning off JS in their browsers, and browsers are free so they can always just upgrade to the latest one.

But as we talked about earlier, most JS failures are not because someone deliberately disabled it, and people don’t always have a choice over which browser they use.

Progressive enhancement, or building in layers, adds what Jeremy Keith calls “fault tolerance”20 to your site or app and helps ensure as much of is as as usable as possible to as many people as possible.

There’s a belief that progressive enhancement adds a lot of work, but it doesn’t have to.

For example, let’s say you wanted to use the GitHub API and some JavaScript to show a list of your repositories on your site. Rather than using a blank div, add a link to your GitHub account.

<div id="github">
	<a href="https://github.com/cferdinandi">View my projects on GitHub</a>
</div>

Once the required JS is loaded, you can use the API to get a list of repositories and replace the existing link with the data from GitHub.

If that file fails to load for some reason, people still get something usable.

<div id="github">
	<h3>My latest projects on GitHub</h3>
	<ul>
		<li><a href="https://github.com/cferdinandi/smooth-scroll">Smooth Scroll</a></li>
		<li><a href="https://github.com/cferdinandi/reef">Reef</a></li>
		<li><a href="https://github.com/cferdinandi/atomic">AtomicJS</a></li>
	</ul>
</div>

Progressive enhancement is for CSS, too

People usually think of progressive enhancement a JavaScript thing, but it applies to CSS, too.

CSS Grid makes it a lot easier to create really innovative layouts that were quite difficult in the past. But, it only works in modern browsers. IE and older versions of Edge don’t support it.


But… you can treat layout like a progressive enhancement.

Browsers that support it will get the fancy layout, while browsers that don’t will get a simpler, single-column layout.

Inclusive Design

In many ways, the web is inclusive “out-of-the-box,” and we ruin it with the choices that we make.

Our designs and code break the way people who use a keyboard instead of a mouse to navigate move around the page. They break how screen readers consume the content.

Our focus on only the latest browsers and faster internet connections breaks large portions of the web for people in low-bandwidth countries and lower income households.

The web is for everyone, but too often we build sites and apps as if it’s only for ourselves.

This stuff can be hard, and I struggle with accessibility often. One amazing resource is the A11Y Project.21 It includes a ton of accessible design patterns, and a checklist you can run through.

I also love Dave Rupert’s A11Y Nutrition Cards.22

They include quick overviews of the keyboard behaviors, focus behaviors, and required HTML elements and attributes for a variety of JS-driven components.

Just say “no”

Building inclusive experiences often means saying “no” to people even when it’s uncomfortable to do so.

It means saying “no” to other designers or developers on your team who want to do things in a way that breaks functionality for a segment of people. It means saying “no” to clients who want you to do something that’s bad for the user.

It means saying “no” to your boss when you’re told to implement some executive’s pet feature.

It’s hard to just say no, but you’re a web professional, and its your professional obligation to speak up.

Do any companies actually use Lean Web principles?

Whenever I talk about building websites like this, I’m typically asked if any companies actually build websites and web apps like this.

Yes, they do!

I maintain a list of organizations that build sites and apps the “lean web way” at vanillaJSlist.com.23 It includes some pretty well-known companies.

At the end of 2018, GitHub removed jQuery from their app.24 Instead of replacing it with a modern framework, they opted for native browser methods and custom web components.

I interviewed Keith Cirkel, one of the developers involved on the project. He told me that one of their internal mottos is, “Build websites like its 2005.”

Also in 2018, Netflix ripped React out of their default frontend page load (they still use React server-side for templating).25

By using vanilla JS for their client-side code, Netflix’s Load Time and Time-to-Interactive decreased by 50%.

MeetSpace is a video messaging app. They wrote their app entirely in vanilla JS,26 because they wanted the experience to be as fast as possible.

Founder Nick Gauthier wrote,

Using this approach, we were able to create an incredibly fast and light web application that is also less work to maintain over time.

What now? →

Chapters

  1. Intro
  2. Modern Best Practices
  3. How did we get here?
  4. Lean Web Principles
  5. What now?