One of the big reasons to use jQuery, for a long time, was how easy it made Ajax. It has a super clean, flexible, and cross-browser compatible API for all the Ajax methods. jQuery is still mega popular, but it’s becoming more and more common to ditch it, especially as older browser share drops and new browsers have a lot of powerful stuff we used to learn on jQuery for. Even just querySelectorAll is often cited as a reason to lose the jQuery dependency.
How’s Ajax doing?
Let’s say we needed to do a GET request to get some HTML from a URL endpoint. We aren’t going to do any error handling to keep this brief.
jQuery would have been like this:
$.ajax({
type: "GET",
url: "/url/endpoint/",
}).done(function(data) {
// We got the `data`!
});
If we wanted to ditch the jQuery and go with browser-native Ajax, we could do it like this:
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = ajaxDone;
httpRequest.open('GET', '/url/endpoint/');
httpRequest.send();
function ajaxDone() {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
// We got the `httpRequest.responseText`!
}
}
}
The browser support for this is kinda complicated. The basics work as far back as IE 7, but IE 10 is when it really got solid. If you wanna get more robust, but still skip any dependencies, you can even use a window.ActiveXObject fallback and get down to IE 6.
Long story short, it’s certainly possible to do Ajax without any dependencies and get pretty deep browser support. Remember jQuery is just JavaScript, so you can always just do whatever it does under the hood.
But there is another thing jQuery has been doing for quite a while with it’s Ajax: it’s Promise based. One of the many cool things about Promises, especially when combined with a „asynchronous” even like Ajax, is that it allows you to run multiple requests in parallel, which is aces for performance.
The native Ajax stuff I just posted isn’t Promise-based.
If you want a strong and convenient Promise-based Ajax API, with fairly decent cross-browser support (down to IE 8), you could consider Axios. Yes, it’s a dependency just like jQuery, it’s just hyper-focused on Ajax, 11.8 KB before GZip, and doesn’t have any dependencies of its own.
With Axios, the code would look like:
axios({
method: 'GET',
url: '/url/endpoint/'
}).then(function(response) {
// We got the `response.data`!
});
Notice the then statement, which means we’re back in the Promise land. Tiny side note, apparently the requests don’t look identical to jQuery on the server side.
Browsers aren’t done with us yet though! There is a fairly new Fetch API that does Promise-based Ajax with a nice and clean syntax:
fetch('/url/endpoint/')
.then(function(response) {
return response.text();
})
.then(function(text) {
// We got the `text`!
});
The browser support for this is getting pretty good too! It’s shipping in all stable desktop browsers, including Edge. The danger zone is no IE support at all and only iOS 10.1+.
I got a chance to be on the Thunder Nerds podcast the other week, on the episode 55 – Down Wit SVG? Yeah You Know Me with Chris Coyier. We got to talk about a variety of things that I work on, including Practical SVG, CodePen, this site, ShopTalk, and upcoming conferences. Speaking of which…
And at least two more that I’ll need to wait to mention until they get something online for.
Sarah Drasner also keeps our Guide to 2017 Conferences post up-to-date, if you want to look more broadly at what’s out there.
We’ve shut down the shop here on CSS-Tricks. It’s likely temporary as we kinda revamp the merch in there and freshen things up. No exact plan yet, but of course we’ll let you know when it’s back open again.
Since the new year, Dave and I have been very steadily publishing new episodes of ShopTalk, and we have plenty more lined up. If you’re anything like me, your podcast listening behavior fluctuates and you go in and out of it. If you’re about to go into an „in” phase, might I recommend”
Speaking of CodePen, if you’re a PRO user, you might have already gotten an email about our latest feature, now in BETA. It will be our biggest release ever on CodePen. If you had any sense at all that releases on CodePen were slowing down, you might have been right, because we’ve been so heads-down on this thing for over a year.
We’ve also started sending another weekly newsletter called The CodePen Spark that is loaded with amazing work from that week. We’re already up through week 15!
CodePen Meetups are also hopping. I’m gearing up to go to one tonight, here in Miami where I have been living the past few months. I also recently got to attend the one in Denver, which was huuuuge. I’ve never been to one I didn’t have a good time and learn something at.
They’ve also been as international as ever! We have plenty in the United States, but There are upcoming meetups in places like:
Auckland, New Zealand
Sylhet, Bangladesh
Örebro, Sweden
Bulgaria just had their first! Dublin has had fourteen! If you’d like to host one in your area, you can.
Last but not least, the CSS-Tricks Newsletter gets better all the time. Remember it’s completely hand-written these days to explain all the most interesting stuff that week and much of it is unique to the newsletter.
Web Audio API let’s us make sound right in the browser. It makes your sites, apps, and games more fun and engaging. You can even build music-specific applications like drum machines and synthesizers. In this article, we’ll learn about working with the Web Audio API by building some fun and simple projects.
Getting Started
Let’s do some terminology. All audio operations in Web Audio API are handled inside an audio context. Each basic audio operation is performed with audio nodes that are chained together, forming an audio routing graph. Before playing any sound, you’ll need to create this audio context. It is very similar to how we would create a context to draw inside with the <canvas> element. Here’s how we create an audio context:
var context = new (window.AudioContext || window.webkitAudioContext)();
Safari requires a webkit prefix to support AudioContext, so you should use that line instead of new AudioContext();
Normally the Web Audio API workflow looks like this:
An oscillator is a repeating waveform. It has a frequency and peak amplitude. One of the most important features of the oscillator, aside from its frequency and amplitude, is the shape of its waveform. The four most commonly used oscillator waveforms are sine, triangle, square, and sawtooth.
It is also possible to create custom shapes. Different shapes are suitable for different synthesis techniques and they produce different sounds, from smooth to harsh.
The Web Audio API uses OscillatorNode to represent the repeating waveform. We can use all of the above shown waveform shapes. To do so, we have to assign the value property like so:
You can create a custom waveform as well. You use the setPeriodicWave() method to create the shape for the wave, that will automatically set the type to custom. Let’s listen how different waveforms produce different sounds:
Custom waveforms are created using Fourier Transforms. If you want to learn more about custom waveform shapes (like how to make a police siren, for example) you can learn it from this good resource.
Running the oscillator
Let’s try to make some noise. Here’s what we need for that:
We have to create a Web Audio API context
Create the oscillator node inside that context
Choose waveform type
Set frequency
Connect oscillator to the destination
Start the oscillator
Let’s convert those steps into code.
var context = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = context.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.value = 440;
oscillator.connect(context.destination);
oscillator.start();
Note how we define the audio context. Safari requires the webkit prefix, so we make it cross-browser compatible.
Then we create the oscillator and set the type of the waveform. The default value for type is sine, so you can skip this line, I just like to add it to make it more clear and easy to update. We set the frequency value to 440, which is the A4 note (which is also the default value). The frequencies of musical notes C0 to B8 are in the range of 16.35 to 7902.13Hz. We will check out an example where we play a lot of different notes later in this article.
Now when we know all of that, let’s make the volume adjustable as well. For that we need to create the gain node inside of the context, connect it to the chain, and connect gain to the destination.
var gain = context.createGain();
oscillator.connect(gain);
gain.connect(context.destination);
var now = context.currentTime;
gain.gain.setValueAtTime(1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.5);
oscillator.start(now);
oscillator.stop(now + 0.5);
Now you have some knowledge of working with the oscillator, here’s a good exercise. This Pen has the oscillator code setup. Try to make a simple app that changes the volume when you move the cursor up and down your screen, and changes the frequency when you move the cursor left and right.
Timing of Web Audio API
One of the most important things in building audio software is managing time. For the precision needed here, using the JavaScript clock is not the best practice, because it’s simply not precise enough. However the Web Audio API comes with the currentTime property, which is an increasing double hardware timestamp, which can be used for scheduling audio playback. It starts at 0 when the audio context is declared. Try running console.log(context.currentTime) to see the timestamp.
For example, if you want the Oscillator to play immediately you should run oscillator.start(0) (you can omit the 0, because it’s the default value). However you may want it to start in one second from now, play for two seconds, then stop. Here’s how to do that:
var now = context.currentTime;
oscillator.play(now + 1);
oscillator.stop(now + 3);
There are two methods to touch on here.
The AudioParam.setValueAtTime(value, startTime) method schedules change of the value at the precise time. For example, you want to change frequency value of the oscillator in one second:
However, you also use it when you want to instantly update the value, like .setValueAtTime(value, context.currentTime). You can set the value by modifying the value property of the AudioParam, but any updates to the value are ignored without throwing an exception if they happen at the same moment as the automation events (events scheduled using AudioParam methods).
The AudioParam.exponentialRampToValueAtTime(value, endTime) method schedules gradual change of the value. This code will exponentially decrease the volume of the oscillator in one second, which is a good way to stop sound smoothly:
We can’t use 0 as the value because the value needs to be positive, so we use a very small value instead.
Creating the Sound class
Once you stop an oscillator, you cannot start it again. You didn’t do anything wrong, it’s the feature of the Web Audio API that optimizes the performance. What we can do is to create a sound class that will be responsible from creating oscillator nodes, and play and stop sounds. That way we’ll be able to call the sound multiple times. I’m going to use ES6 syntax for this one:
We pass the context to the constructor, so we can create all of the instances of the Sound class within same context. Then we have the init method, that creates the oscillator and all of the necessary filter nodes, connects them, etc. The Play method accepts the value (the frequency in hertz of the note it’s going to play) and the time when it shall be played. But first, it creates the oscillator, and that happens every time we call the play method. The stop method exponentially decreases the volume in one second until it stops the oscillator completely. So whenever we need to play the sound again, we create a new instance of the sound class and call the play method. Now we can play some notes:
let context = new (window.AudioContext || window.webkitAudioContext)();
let note = new Sound(context);
let now = context.currentTime;
note.play(261.63, now);
note.play(293.66, now + 0.5);
note.play(329.63, now + 1);
note.play(349.23, now + 1.5);
note.play(392.00, now + 2);
note.play(440.00, now + 2.5);
note.play(493.88, now + 3);
note.play(523.25, now + 3.5);
That will play C D E F G A B C, all within the same context. If you want to know the frequencies of notes in hertz, you can find them here.
Knowing all of this makes us able to build something like a xylophone! It creates a new instance of Sound and plays it on mouseenter. You can check the example and try make one by yourself as an exercise.
I’ve created a playground, containing all the required HTML and CSS, and the Sound class we’ve created. Use the data-frequency attribute to obtain the note values. Try here.
Working with a recorded sound
Now that you’ve built something with an oscillator, let’s now see how to work with a recorded sound. Some sounds are very hard to reproduce using the oscillator. In order to use realistic sounds in many cases, you’ll have to use recorded sounds. This can be `.mp3`, `.ogg`, `.wav`, etc. See the full list for more info. I like to use `.mp3` as it’s lightweight, widely supported, and has pretty good sound quality.
You can’t simply get sound by a URL like you do with images. We have to run an XMLHttpRequest to get the files, decode the data, and put into the buffer.
class Buffer {
constructor(context, urls) {
this.context = context;
this.urls = urls;
this.buffer = [];
}
loadSound(url, index) {
let request = new XMLHttpRequest();
request.open('get', url, true);
request.responseType = 'arraybuffer';
let thisBuffer = this;
request.onload = function() {
thisBuffer.context.decodeAudioData(request.response, function(buffer) {
thisBuffer.buffer[index] = buffer;
updateProgress(thisBuffer.urls.length);
if(index == thisBuffer.urls.length-1) {
thisBuffer.loaded();
}
});
};
request.send();
};
loadAll() {
this.urls.forEach((url, index) => {
this.loadSound(url, index);
})
}
loaded() {
// what happens when all the files are loaded
}
getSoundByIndex(index) {
return this.buffer[index];
}
}
Let’s take a look at the constructor. We receive our context there as we did in the Sound class, receive the list of URLa that will be loaded, and an empty array for the buffer.
The we have two methods: loadSound and loadAll. loadAll loops through the list of URLs and calls the loadSound method. It’s important to pass the index, so that we put the buffered sound into the correct element of the array, regardless of which request loads first. This also let’s us see which request is the last, which means that on its completion the buffer is loaded.
Then you can call the loaded() method, which can do something like hiding the loading indicator. And finally the getSoundByIndex(index) method gets the sound from the buffer by index for playback.
The decodeAudioData method has a newer Promise-based syntax, but it doesn’t work in Safari yet:
context.decodeAudioData(audioData).then(function(decodedData) {
// use the decoded data here
});
Then we have to create the class for the sound. Now we have our complete class to work with the recorded sound:
The constructor accepts the context and the buffer. We create by calling createBufferSource() method, instead of createOscillator as we did before. The buffer is the note (element from the buffer array) that we get using the getSoundByIndex() method. Now instead of the oscillator we create a buffer source, set the buffer, and then connect it to the destination (or gain and other filters).
let buffer = new Buffer(context, sounds);
buffer.loadAll();
sound = new Sound(context, buffer.getSoundByIndex(id));
sound.play();
Now we have to create an instance of buffer and call the loadAll method, to load all of the sounds into the buffer. We also have the getSoundById method to grab the exact sound we need, so we pass the sound to the Sound and call play(). The id can be stored as a data attribute on the button that you click to play the sound.
Here’s a project that uses all of that: the buffer, the recorded notes, etc:
You can use that example for for reference, but for your own exercise, here’s a playground I’ve created. It has all the necessary HTML and CSS and the URLs to the notes that I have recorded on a real electric guitar. Try writing your own code!
Intro to Filters
The Web Audio API lets you add different filter nodes between your sound source and destination. BiquadFilterNode is a simple low-order filter which gives you control over what parts of the frequency parts shall be emphasized and which parts shall be attenuated. This lets you build equalizer apps and other effects. There are 8 types of biquad filters: highpass, lowpass, bandpass, lowshelf, highshelf, peaking, notch, and allpass.
Highpass is a filter that passes higher frequencies well, but attenuates lower frequency components of signals. Lowpass passes lower frequencies, but attenuates higher frequencies. They are also called „low cut” and „high cut” filters, because that explains what what happens to the signal.
Highshelf and Lowshelf are filters are used to control the bass and treble of the sound. They are used to emphasize or reduce signals above or below the given frequency.
You will find a Q property BiquadFilterNode interface, which is a double representing the Q Factor. Quality Factor or Q Factor control the bandwidth, the number of frequencies that are affected. The lower the Q factor, the wider the bandwidth, meaning the more frequencies will be affected. The higher the Q factor, that narrower the bandwidth.
You can find more info about filters here, but we can already build a parametric equalizer. It’s an equalizer that gives full control for adjusting the frequency, bandwidth and gain.
Let’s take a look on how we can apply distortion to the sound. If you wonder what makes an electric guitar sound like one, it is the distortion effect. We use the WaveShaperNode interface to represent a non-linear distorter. What we need to do is to create a curve that will shape the signal, distorting and producing the characteristic sound. We don’t have to spend a lot of time to create the curve, as it’s already done for us. We can adjust the amount of distortion as well:
I think you’d do well to read Cathy O’Neils Weapons of Math Destruction: How Big Data Increases Inequality and Threatens Democracy. I saw her read at the Miami Book Fair several months ago and immediately bought a copy. I even got her to sign it which is kinda cool 😉
Cathy’s big idea is that we’re absolutely surrounded by algorithms that inform big decision making. There are lots of good algorithms that help us. Sadly, there are lots of insidiously, dangerous, bad algorithms that do serious damage, and they are lurking all about disguised as good algorithms.
One aspect of a good algorithm is some kind of feedback and correctional system. Early on Cathy points to some advertising algorithms as an example of a healthy algorithm. For example, if an algorithm is in place to recommend a product you should buy, and it does a terrible job at that, it will be tweaked until fixed, thereby correcting what is has set out to do. Moneyball-style algorithms are the same. The data is open. Baseball team managers use algorithms to help recruit for their team and manage how they play. If it isn’t working, it will be tweaked until it does.
A bad algorithm might lack a feedback loop. One of her strongest examples is in the algorithms that rate teachers. There is plenty of evidence that these algorithms are often wrong, ousting teachers that definitely should not have been. And not in a „they tested badly, but have a heart of gold” way, in a „the algorithm was actually just wrong” way. What makes something like this a „weapon of math destruction” (WMD) then, is the fact that it affects a lot of people, screws up, and there is no correction mechanism. There are lots of interesting criteria, though. I’ll let you read more about it.
There is an awful lot of considerations and nuance here, and I think Cathy delivers pretty gracefully on all that. She has an impressive pedigree academically, professionally, and journalistically. There is some pitchfork raising here, but the prongs are made of research, data, and morals.
I was strongly reminded about the scariness of non-secure websites the other day.
I’m using Xfinity as an internet service provider, and they give you a device that is both a cable modem and a router.
Here’s a tiny bit of backstory. I use a VPN, and I discovered that in using their modem directly, the VPN wouldn’t work. I’m not sure why. I didn’t dig into it very far, because I have a modem of my own I’d prefer to use. So I plugged that in, which worked… but not particularly well. The connection was spotty and slow, even right in my own house.
I think (maybe?) it was competing WiFi signals from the two routers sitting right next to each other. Don’t quote me on that. The reason I think that is because, fortunately, I was able to turn off the router on the Xfinity device, and that solved the problem. Thde speed and connectivity was back. To their credit, it was really fast. The Xfinity device has a featured called „Bridge Mode” that is specifically for turning off the router so that you can use your own. I was able to enable that, use my own router, get the speed back, and connect to the VPN.
Win! That lasted for a few months. Then recently there was some weird big internet outage in our area. Xfinity notified us about it. They had to push some updates or something to our device, and that broke everything again. I struggled with it for days, but what ultimately worked was turning off Bridge Mode, and turning it back on again (isn’t it always?).
In those in-between days, the only thing I could figure out to get online was to connect to the SSID „xfinitywifi” that this router seemed to be emitting. This „xfinity” network is unusual because it behaves kinda like a coffee shop or university hotspot in that it pops up that weird browser modal and you have to log in with your (Xfinity) credentials. It’s a value-add kinda thing for their service. Their routers are dotted all over the place, so if you’re a customer of theirs, you get internet („for free”) a lot of places. My fiance was at the doctor the other day, and she was using it there.
If that’s the network you’re connected to, Xfinity performs man-in-the-middle attacks on websites to send you messages. Here’s an example of me just looking at a (non-secure) website:
Man-in-the-middle, meaning, this website had no such popup in its code. Xfinity intercepted the request, saw it was a website, and forcefully injected its own code into the site. In this case, to advertise an app and to tell you about security. Ooozing with irony, that.
If they can do that, imagine what else they can do. (Highly recommended listening: ShopTalk #250) They could get even more forceful with advertising. Swap out existing advertising with their own. Install a keylogger. Report back information about what you’re doing and where you are. You might not even know if anything is happening at all.
This might seem a little tin foil hatish, but realize: they’ve already been incentivized to do this. All the incentive is there to keep milking value out of this superpower they have.
Some good news: Individual websites can stop this with HTTPS. That’s a massively good step. With HTTPS, the traffic packets are encrypted and Xfinity can’t read or manipulate them effectively. Through metadata, they might be able to guess what they are (e.g. know you’re streaming a video and throttle speed), but there isn’t much else they can do.
It’s not just this one indiscretion, Xfinity also uses this tactic to send you other messages.
@chriscoyier@XFINITY also how they warn you about bandwidth or billing issues. not fun.
Seriously?! You require me to have a box in my house that broadcasts a public WiFi hotspot that I can’t turn off? You’re automatically opted into it, but you can turn it off.
Seriously?! You use that hotspot to perform man-in-the-middle attacks on anybody using it?
I’m sure it’s not just Xfinity, it’s just that’s what I’m using now and have now seen it with my own eyes. To be clear, I’m sure I signed something that allows them to do everything they are doing and I don’t think anything they are doing is technically illegal (again, don’t quote me on that).
Being upset at them, and telling them about it, is a good step. Fighting back is another. Internet access is vital, so you have to use something, but if you have an option, is there an ISP that doesn’t do this available to you? Use them. Money talks.
Again, HTTPS solves this on a per-website basis. Jeff Atwood sums this up pretty well:
You have an unalienable right to privacy, both in the real world and online. And without HTTPS you have zero online privacy – from anyone else on your WiFi, from your network provider, from website operators, from large companies, from the government.
Using HTTPS means nobody can tamper with the content in your web browser. This was a bit of an abstract concern five years ago, but these days, there are more and more instances of upstream providers actively mucking with the data that passes through their pipes. For example, if Comcast detects you have a copyright strike, they’ll insert banners into your web content … all your web content! And that’s what the good guy scenario looks like – or at least a corporation trying to follow the rules. Imagine what it looks like when someone, or some large company, decides the rules don’t apply to them?
The move to HTTPS is non-trivial, and introduces somewhat complicated dependencies. It’s easy to forget to renew your certificate and break your entire website just like that. I’m not arguing against HTTPS (exactly the opposite), but you should know that it requires some upfront work and some diligent maintenance.
If you’re on WordPress like me, I wrote up how I moved to all-HTTPS going on two years ago. It involved a little database work even, getting URL’s pointing to the right places.
SSL certificates (the main prerequisite for HTTPS) also have traditionally cost money. No more! Let’s Encrypt is here:
Lets Encrypt is a free, automated, and open Certificate Authority.
There is an in-progress WordPress plugin for it. Let’s hope that gets off the ground. Just a few days ago I used the Let’s Encrypt Plesk extention to put HTTPS on ShopTalk’s website and it took me like 5 minutes. I’ll have to write that up soon.
A community site to help site owners migrate to HTTPS with a simple tested process. Allowing you to filter the plan based on multiple platforms (WordPress, Magento, and more), hosting environments (cPanel, Apache, and more) along with the level of control / access you have over the site.
For the past few weeks there has been lots of talk about HTML headings in web standards circles. Perhaps you’ve seen some of the blog posts, tweets, and GitHub issue threads. Headings have been part of HTML since the very first websites at CERN, so it might be surprising to find them controversial 25 years later. I’m going to quickly summarize why they are still worth discussing, with plenty of links to other sources, before adding my own opinions to the mix. If you’re up-to-date on the debate, you can jump straight to the „Bigger Dilemma” section.
The Story So Far…
HTML uses headings (<h1>, <h2>, <h3>, and so on until <h6>) to mark up titles for a subsequent section of text. The numbers (or levels) of the heading elements are supposed to logically correspond to a tree-like structure of nested sections, like books that have chapters with sections and sub-sections.
However, HTML markup did not originally have a way to reflect this nested logical structure in a nested DOM structure. Unlike nested lists, nested headings weren’t actually nested in elements that defined the parent sections. Heading elements of different levels were all sibling elements, and also siblings to the paragraphs they provide a title for. The „sections” were a purely logical structure, not a DOM structure, containing all markup that starts with a heading and continued until you reached another heading of the same or higher level.
As Brian Kardell points out, this made perfect sense in the „flat earth markup” of early HTML, where tags were just typographic instructions inserted into a flow of text. The concept of an HTML page as a tree structure came later, when so-called Dynamic HTML needed a document object model (DOM) to describe that flow of text and tags as a data structure that scripts could access.
Not to spoil the ending, but HTML now has a <section> element which can (optionally) be used to create a nested DOM structure to match your logical heading structure. The <main>, <header>, <footer>, <article>, <aside>, and <nav> elements all also help create a nested document structure that is reflected in DOM nesting.
But there was another problem with the originally heading model: it couldn’t easily be remixed in template systems. Because the heading level is expressed by the tag name (<h1> versus <h4>), rather than by the context in which it’s used, you can’t easily re-use the same content in a different context where the level would be different. For example, a blog might use the same set of article headlines and intro paragraphs in many contexts: as stand-alone blog post pages; as abstracts on a main index page; or as abstracts on an archive page which also has headings dividing the list by month or year. What heading level should the article title be?
Early proposals for sectioning elements also included a level-free <h> or <heading> element, that would be assigned a level based on context. (In fact, the idea goes back to the earliest discussions of HTML.) But when sectioning elements were finally added to HTML, they were designed to work with the existing heading elements. However, the specifications defined a „Document Outline Algorithm” which would re-calculate the heading levels for the existing numbered heading tags, based on section nesting.
With the Document Outline Algorithm, you could (theoretically) use an <h1> for all headings, and the browser would figure out the level of each heading based on its nesting within <article>, <section>, and related elements. The outline algorithm would ensure that the top heading in the page would be a level 1, and that all other headings would be nested in a consistent order, with no levels skipped. The WHATWG version of the outline also defines rules for dealing with multi-part headings in <hgroup> elements, so the sub-headings do not create sub-sections. (The W3C version of HTML 5 instead declared <hgroup> obsolete: multi-part headings should be marked up as paragraphs inside a section <header> or spans inside the main heading element.)
Browsers modified their default styles so that headings inside of nested sections would have progressively smaller font sizes (just like how the default style for <h3> has smaller font than <h2>, which is smaller than <h1>). But they didn’t change the way they exposed heading levels to the accessibility APIs that are used by screen readers. And screen-reader users are the only ones who really experience heading levels as part of the user interface.
Screen readers announce the numbered level when reading headings, and they allow users to quickly scan through headings of a given level. According to a WebAIM survey, two-thirds of screen-reader users scan headings as the first step of trying to find information on a long web page. For these users, the only effect of the Document Outline Algorithm was that some new pages (eagerly adopting the new spec) were presented as flat lists of level-one headings, with no structure at all.
Why won’t browsers use the outline algorithm for accessible heading levels? Many arguments have been made, but the most compelling one is that it could alter the way existing web sites are presented to screen-reader users, and it’s not clear that those alterations would mostly be positive.
Adrian Roselli has compiled a good overview of the discussions about the problems caused by the unimplemented outline specification, in „There is No Document Outline”. The latest W3C HTML specs only use the document outline algorithm to suggest how authors should synchronize their numbered heading tags with their nested sectioning elements. The WHATWG HTML specs still have the full outline algorithm described as a normative requirement, although there is an open issue where many suggest removing it altogether. As WHATWG spec editor Domenic Denicola puts it:
At some point we cannot claim that user agents are broken. They are instead rejecting our change request.
The Current Debate
The latest flurry of debate was sparked when Jonathan Neal filed an issue on the W3C HTML spec re-proposing the elusive <h> element. The key to the proposal is that an <h> heading element could have a nesting level defined by sectioning elements, while still allowing the existing numbered heading tags to have the level determined by their tag name. Authors would opt in to the outline algorithm by using the new tag. Until browsers supported <h>, a JavaScript (or server-side) polyfill could calculate the heading levels and add them into the DOM with ARIA attributes: role="heading" and aria-level="3" tell the browser to treat an element as a level-3 heading for accessibility purposes, regardless of tag name or nesting, so the page author ends up fully responsible for any heading confusion.
The work needed to fix the existing web is a subset of creating a new element that does the same thing, but doesn’t fix the existing web.
In other words, if the outline algorithm is so great that it’s worth a new element, why not just implement the outline algorithm for existing elements instead?
There is a hidden assumption below all the discussion of how to create a document outline for a web page. Discussing how to create the document outline assumes that the structure of a web page can be defined as an outline: as a tree where the nesting level of a heading defines its importance.
I personally don’t think a simple nested outline can capture all the levels of meaning that are conveyed by HTML heading levels, as they are used on the web. I’ll get to why in a bit. But there’s a reason that all the discussion has focused on this type of outline: because this is the type of outline screen readers expect.
For most web users, and web authors, the document outline is irrelevant. They do not know and do not care how the headings and sections are nested, they only see what’s on the screen. And what’s on the screen, in most web pages today, is a two-dimensional layout of content, some of it nested, but some of it independent, with each part given implied importance and relationships by layout, colors, and typography.
So, the question we should be debating isn’t „How should we assign outline levels to headings?” It’s: „How can we summarize the meaningful structure of a web page, so that people using assistive technology can easily find content?”
I’d personally love it if browsers added a feature, for all users, to show the outline as a table of contents, and make it possible to quickly navigate to headings with the keyboard. Maybe if they did, more web authors would pay attention to what their outline looked like. But the browsers don’t, and so most authors don’t.
If you do want to see what your website’s heading outline looks like—and how it would theoretically look like using the document outline algorithm—you can use the W3C Nu HTML validator service, with the Show Outlines option checked.
As it currently stands, the document outline is only of daily importance to screen-reader users, and those users are currently used to dealing with the mess of erratic heading levels in web pages. I’m sure many screen-reader users would appreciate heading levels being fixed. But fixing headings for screen-reader users doesn’t just mean creating a tree of neatly nested headings with no skipped level numbers. It means creating a heading structure that accurately reflects the meaning intended by the creators of the web page, the meaning that visual users infer from style and layout. And in order to do that, we need to consider how meaning is communicated to all the users of web pages who aren’t hearing each heading announced with a numerical level.
A Language is Defined by Those Who Speak It
HTML is unique among computer code languages, because it defines so many constructs without assigning them specific behavior. Meaning in computer code is known as the semantic side of the language, as opposed to the syntactic structures of its grammar. But in most programming languages, the semantic aspects of built-in objects are still strongly tied to instructions for the computer. In JavaScript, new Date() and new Promise() have the same syntax—calling a constructor function—but your JS interpreter understands the semantic distinction between the two object names, and behaves very differently for each.
In contrast, an HTML <article> or a <section> doesn’t come with any instructions for what your web browser should do with it (other than the un-implemented outline algorithm). Instead, the difference between the two is all about the meaning of the content, a way to provide machine-readable annotations for the information communicated from one human being, the website author, to another: the reader.
Meaning in human communication is difficult to define, and never static. But most importantly of all, it is defined by the people who use the language. Dictionaries compile summaries of the meanings that are used, but they don’t restrict them. If people start using words in new and different ways, the dictionary (if it’s any good) will update their definitions.
When I was in grade school, a librarian showed off the multi-volume Oxford English Dictionary by introducing us to a selection of wild and crazy words. Google* was the name for the number that would be written as a 1 followed by 100 zeros (10100, in scientific notation). Crazy, right? Who would ever need to know a word like that? But times change. In 2006, the OED added a new definition, google as a verb (meaning to use the Google search engine), which might be used a google times more often than the number quantity in modern English conversation.
*Correction: As Mark notes in the comments, the correct spelling of the word I was shown all those years ago is actually googol. And now I don’t know what to believe anymore.
When it comes to the meaning of HTML tags, the equivalent of dictionaries are the two competing HTML specifications (WHATWG and W3C). And just like dictionaries, they both started out as efforts to describe the language as it was currently used.
The fact that there are two different HTML specifications make discussing changes more difficult, but it also strongly highlights the collective, consensus-based nature of HTML as a language. There is no one defining document setting the rules for HTML. HTML is defined by the people who write it and by the web browsers that interpret it.
But it’s not that simple, of course. HTML isn’t only used by human beings, it is also used by computers. And computers aren’t very good about handling fuzzy and shifting meaning.
Whenever you ask a computer to do something with your content—like, for example, tell the screen reader what headings there are in this website and how they are organized—it needs clear and explicit rules for how to do so. If some web authors are using heading tags in one way, and some authors are using the same tags with different meaning, your browser is going to need additional rules to figure out which is which—or else it’s going to get it wrong, at least some of the time.
The driving force of the web standards movement was a hope that all web browsers would react to web page code in (approximately) the same way. And that means defining new features in standards documents before they can be used on the web. Instead of being descriptive, like a dictionary (defining how things are), they are prescriptive, like a legal code (defining how things should be).
The slow pace of developing standards, with lots of input from browser teams, is supposed to ensure that the end result is both prescriptive and descriptive, at least for the parts of the language that describe browser behavior. But it doesn’t always work. There are lots of details in both specs that don’t match actual browser behavior. The W3C’s issue repo even has a comfortingly-named Match Reality Better label aimed at fixing these bits.
And that’s just for the features that describe what browsers should do. What about all the HTML elements that define the semantics of content? Shouldn’t those „match reality better,” too?
A few months ago Sara Soueidan suggested to the W3C HTML working group that maybe the <address> element should be valid for all addresses (and not just page-owner contact addresses). Many people before her have certainly made the same complaint. But this time, something happened. Following a little rough data scraping, which suggested that actual usage in the wild wasn’t restricted to the original definition, the definition in the W3C specs was updated.
Does it make any difference? Maybe not. Browsers don’t do anything with <address> except make it italic. And the WHATWG HTML specs still have the old definition. But it means the spec comes a little closer to describing the way code is actually used on the web, not how someone once imagined it might be.
Which brings us back, at last, to headings: How are they actually used on the web? And is it even possible to define a prescriptive set of instructions, for web authors and for web browsers, that ensure that the meaning of headings can be correctly communicated to screen readers (and potentially, other software)?
The Many Meanings of Headings
What is a heading? It’s a short title for a section of a document. The heading for this section is „The Many Meanings of Headings.” So far, so good.
But all headings are not created equal.
There are big headings:
A Big Heading
and there are much smaller headings:
A heading so small it’s barely a heading
If you inspect the code, you’ll see that one of those is an <h1> and the other is an <h6>. Both of them are wrapped in <figure> tags, which—according to the document outline algorithm—should encapsulate them and keep them from messing up the main document outline. But we all know by now that the document outline algorithm isn’t actually used by web browsers, so apologies to any screen reader users who ended up halfway down the article by mistake.
For anyone reading this article with their eyes in a modern browser, the difference between the two headings is communicated by the font size, and possibly the font style. The exact details will depend on whether you’re looking at the website’s CSS or your browser’s reading mode CSS, and on how recently Chris has changed CSS-Tricks’ styles. But unless Chris has really messed things up, it will be pretty clear to visual readers that the <h1> is bigger and more important than the <h6>. We could change the CSS so they looked identical, but at this point it is hard to understand why you would want to do that. If you wanted them to look the same, why not use the same tag name?
So let’s go a step further, and put those two headings together with some filler text in between. Here’s one way we could do that, with a main heading, some text, then a sub-heading and some more text:
If you’re only looking at the result tab of those pens, and using your eyes to do so, you might be forgiven for thinking the second and the third are identical, and very different from the first. Visually, both example #2 and example #3 have a main section with a big heading and a sidebar section with a minor heading. The difference is that one uses <div> elements to create the structure and the other uses HTML sectioning elements.
If you’ve read this far, you probably won’t be too surprised to discover that these two examples create different structures when processed by the HTML document outline algorithm. Under that algorithm, divs are ignored, so Example #2 would be treated the exact same as example #1: a main heading, some paragraph text, then a sub-heading and another paragraph. The outline does not indicate at all that the sidebar is a separate, parallel structure to the main article:
A big heading
A heading so small it’s barely a heading
In contrast, if I run the outline algorithm on Example #3, It tells me that there is an unlabelled main document (no top-level heading), with two equal sibling child elements (both headings treated as level-2). So now it clearly conveys the parallel structure, but not the difference in heading importance:
[body element with no heading]
A big heading
A heading so small it’s barely a heading
I don’t think either of these outlines accurately describes that visual layout. Neither does the outline based on tag names, which not only treats the sidebar as nested in the main article, but also gets distracted by my use of <h6> in a page without any <h2/3/4/5> elements.
If I was asked to describe this layout to someone, I would tell them two things:
there are two, side-by-side sections;
one of those sections is more important than the other.
The relative importance of the components is a separate piece of information from the nesting structure—or lack thereof, in this simple example. In a more complex example, you’ll have some chunks of content with meaningful nested headings (like this article), and other chunks (like the sidebars, or the comment section below) that have parallel, independent outlines whose inner heading levels are un-related to the ones in the first chunk. Treating each parallel chunk as equal ignores the relative importance they were given in the markup. But tacking those extra headings onto the end of the main article, just because there isn’t a bigger heading in between, seems somehow worse.
Even when components are nested, they often have an importance level that is independent of the number of sections that surround them. I write books on SVG for O’Reilly. The markup we use to create the books is converted to HTML. The book (level-1 heading) has chapters (level-2 headings) with sections (level-3 headings) that sometimes have sub-sections or even sub-sub-sections (level-4 and 5). But it also has examples, and warning notes, and sidebars, all of which can have their own headings which will be styled identically irrespective of whether that component is in a regular section or a sub-sub-section. If we used the “correct” HTML heading elements, they would have different tag names, depending on the section depth, but would be styled identically.
In web design and in content management, we have two very different ways of talking about the level of a heading: the level of importance, or the level of nesting. I think that the main reason web standards folks can’t agree on an algorithm for turning headings into an outline is because people want an algorithm in which both agree, and they often don’t.
Maybe what’s really needed is to stop talking about outlines as if they re-number heading importance levels. Stop telling web developers they are wrong for using the heading levels that make sense for their content. Let context define the outline nesting, but don’t define outline nesting as if it was interchangeable with tag names. Ideally, find a way for browsers to communicate to screen readers both the nested structure of sections and the raw heading-level numbers, so the screen readers can let their users navigate by nesting structure, while still communicating the relative importance of each heading.
Then focus on the real question:
How can we summarize the meaningful structure of a web page, so that people using assistive technology can easily find content?
My instinct is that the outline that uses sectioning elements is usually better for navigation than sections based only on tag names, but that the details need to be improved. In particular:
There need to be better rules for collapsing un-named sections, maybe treating them as ARIA groups instead of as additional nesting levels in the outline.
There may need to be better rules for handling multi-part headings grouped by an <hgroup> or <header> element.
And there probably need to be better rules about which elements (if any) encapsulate their child headings from the main outline altogether.
Show Me the Data
But that’s just my opinion.
In order to get browsers or screen-readers to change their behavior—let alone to convince all the hundreds of thousands of web developers who are using headings in their content—we are going to need more than hunches and opinions. As I argued early on, we need some data. Both Jake and Brian have echoed that call.
But the kind of data we need isn’t the kind that can be collected by a web crawler. We need data about meaning, the kind of meaning that only real human brains can provide.
The HTML sectioning elements have been around for years now. They aren’t theoretical anymore. They are part of the language that you, web developers, use to communicate. If you’re using sectioning elements, hopefully you have a reason why. When you select a heading tag, hopefully you have a reason why. It’s time to review the HTML standards to make sure they reflect the reasons and meaning used by most developers.