Skip to main content Accessibility Feedback

How do you use the platform when platform features aren't evenly distributed?

One of the often repeated phrases among lean web evangelists like me is to “use the platform” whenever possible.

That means using relying on vanilla, out-of-the-box HTML, CSS, and JavaScript over libraries whenever you can.

The modern web has tons of amazing capabilities that make building for the web much easier than when I was learning. And browsers are much better at implementing features in a cross-compatible way.

But… that doesn’t mean platform features are evenly distributed.

Different browser vendors prioritize different things. Chrome often releases JavaScript features before Firefox or Safari. For years, Firefox had CSS features that weren’t available in other browsers yet. Recently Safari’s been releasing some stuff before anyone else.

As a result, some modern features end up available, but not for everyone. How do you deal with that?

Option 1: Polyfills

A polyfill is a term coined by Remy Sharp for a snippet of code that adds support for a feature to browsers that don’t offer it natively.

While you can sometimes polyfill CSS features, they work best for JavaScript and interactive HTML elements (like details and summary).

With a polyfill, you author your code as if the feature is universally supported in all browsers.

The polyfill code checks if the browser has native support for the feature, and if not, creates it and implements it to the browser spec for you. For example, here’s a polyfill for the (now universally supported) Array.prototype.forEach() method.

// If the forEach() method doesn't already exist on the Array.prototype
if (!Array.prototype.forEach) {

	// Attach a function called forEach() to the Array.prototype
 	Array.prototype.forEach = function (callback, thisArg) {

 		// Use a traditional for loop to loop through the array
 		// and run the callback function
		thisArg = thisArg || window;
		for (var i = 0; i < this.length; i++) {
			callback.call(thisArg, this[i], i, this);
		}

	};

}

The best thing about polyfills is that they’re designed to be deleted. Once browser support catches up, you can delete the polyfill without having to refactor any of your code.

Option 2: Transpiling

Many things can be polyfilled, but not everything can. Notably, Operators and expressions cannot.

To polyfill things like methods, properties, and APIs, you can attach them to the appropriate JavaScript object or create a new class.

But something like template literals or let and const? There’s no way to polyfill those.

For that, you need transpilation.

To transpile code, you run it through a build tool like Babel or ESBuild. They take your modern JavaScript and convert it into something older and better supported.

For example, let’s again imagine that the Array.prototype.map() method was not well supported (it is!), and that let, const, template literals, and arrow functions weren’t either (they are!). You’d write code like this…

// An array of wizards
let wizards = ['Merlin', 'Gandalf', 'Radagast'];

// An HTML string for an unordered list
let html = `<ul>${wizards.map((wizard) => {
	return `<li>${wizard}</li>`;
}).join('')}</ul>`;

Your transpiler might spit out something like this…

// An array of wizards
var wizards = ['Merlin', 'Gandalf', 'Radagast'];

// An HTML string for an unordered list
var html = '<ul>';
for (let i = 0; i < wizards.length; i++) {
	html += '<li>' + wizard + '</li>';
}
html += '</ul>';

These days, transpilers have a much smaller role in web development (in my opinion), but depending on the project, they can still have their place.

Option 3: Progressive Enhancement

For features that are non-critical, progressive enhancement is a great approach!

I use this approach a lot for CSS features. If the modern CSS isn’t supported and it not working will not result in a broken site, I treat it as a progressive enhancement.

A dated example of this would be modern layout features like CSS Grid or Flexbox. You could use them to create a layout, and if they’re not supported, the user gets a simple single column layout instead. It’s not as fancy, but it still works!

For a while, I treated the details and summary elements as a progressive enhancement as well.

If the user’s browser didn’t support them, they’d get all of the content with a heading (though not a semantic heading element) above it by default. It wasn’t as pretty, but it was still perfectly usable.

Which approach should you use?

I generally use a mix of approaches on any particular project.

For years, I favored polyfills. These days, most of my JavaScript is “nice-to-have” rather than essential, so I tend to rely on progressive enhancement a lot more.

If you want to learn how to do more stuff like this, though, you might want to sign up for a free account with the Lean Web Club.

You’ll learn a simpler way to build for the web with HTML, CSS, and vanilla JS, and get instant access to a growing collection of short, focused tutorials and projects on how to build things for the web.