# entry

guid: https://htmhell.dev/adventcalendar/2023/5/
title: The Hellish History of HTML: An incomplete and personal account
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/5/
hash: � �w��~ r�{j NK�
attributes: {"enclosures":[]}
content:
by Jason Cranford Teague<br><figure data-sanitized-class="u-mb">
<a href="https://htmhell.dev/adventcalendar/2023/5/images/HellishHistory.png">
<img src="https://htmhell.dev/adventcalendar/2023/5/images/HellishHistory.png" alt="This timeline shows the adaptation of each version of HTML, along with other key technology releases, phases in the Web design &amp; development, Web eras, and the dominant browser at the time." width="740" height="438">
</a>
<figcaption>Timeline of HTML from 1990–2024</figcaption>
</figure>
<p data-sanitized-class="highlight">
<strong>Note:</strong> HTML standards are developed first in browsers, so the version might have already became the de facto standard before the official standard document is released.
</p>
<p>The story so far:</p>
<p>In the beginning Tim Berners-Lee created the World Wide Web. This has made a lot of people <a href="https://lareviewofbooks.org/article/the-internet-is-a-crime-against-humanity/" title="The Internet Is a Crime Against Humanity">very angry</a> and — putting all of humanity in constant contact with each other — <a href="https://veniceoarsman.com/11932/opinion/essay-how-the-internet-destroyed-the-human-experience/" title="How The Internet Destroyed The Human Experience">been widely</a> <a href="https://www.theatlantic.com/technology/archive/2021/06/the-internet-is-a-collective-hallucination/619320/" title="The Internet Is Rotting">regarded as</a> <a href="https://medium.com/@jamesbridle/something-is-wrong-on-the-internet-c39c471271d2" title="Something is wrong on the internet">a bad</a> <a href="https://www.vanityfair.com/news/2018/07/the-man-who-created-the-world-wide-web-has-some-regrets" title="“I Was Devastated”: Tim Berners-Lee, the Man Who Created the World Wide Web, Has Some Regrets">move</a>.</p>
<p>This would be called the <em>Hypertext Transfer Protocol</em>, but you probably see it every day as <code>http://</code>. Although initially intended as a way to share scientific papers, Berners-Lee quickly realized it would do a lot more than that:</p>
<blockquote>
I designed it for a social effect — to help people work together — and not as a technical toy.
</blockquote>
<p>His vision has led us to today, where the Web is now the predominant information platform for the planet Earth, and the language he created to create documents, HTML, is used by billions.</p>
<p>Unlike other systems at the time that were making use of the growth of home computers and modems — like <a href="https://en.wikipedia.org/wiki/AOL">AOL</a> and <a href="https://en.wikipedia.org/wiki/CompuServe">Compuserve</a> — Berners-Lee provided the World Wide Web as a free and open standard:</p>
<blockquote>
The web is for everyone. It should be accessible to people with disabilities and be available in whatever language they speak. 
</blockquote>
<figure data-sanitized-class="u-mb">
<img src="https://htmhell.dev/adventcalendar/2023/5/images/Tim_tshirt.png" alt="Tim Burners-Lee and Vinton Cerf stand next to each other wearing black t-shirts with white lettering. Tim’s shirt says “I didn’t invent the internet” and Vinton’s shirt reads “I did not event the Web”" loading="lazy" width="530" height="480">
<figcaption>
<p>Tim Berners-Lee (left) did <strong>not</strong> invent the Internet. He did invent the Web and HTML. Vinton G Cerf (right) did <strong>not</strong> invent the Web. He and Robert Kahn did invent the Internet.</p>
</figcaption>
</figure>
<h2 data-sanitized-id="html-the-early-years-(1990---1991)">HTML: The Early Years (1990 – 1991)</h2>
<p>In 1990, the Web was at best an “alpha” version of what it would become. Until then, computer networks were generally limited to a local area (LAN) and required a good deal of computer knowledge to use, much less create. There were other contenders at the time that allowed wider area networks (WAN) that could communicate with computers around a country or around the World. Most notable of these was the Internet. My favorite (and arguably the most popular) way of traveling the Internet in the early 1990s was <a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">Gopher</a>. Gopher was (and technically still is) a way to retrieve documents from the Internet stored on a server. However, it quickly fell out of favor after HTTP arrived on the scene. This is, probably for the best. Can you imagine if we were all surfing the Gopher today?</p>
<p>What HTTP had going for it was HTML. Every single web page ever created uses the <em>Hypertext Markup Language</em>. Tim based HTML off of <em><a href="https://en.wikipedia.org/wiki/Standard_Generalized_Markup_Language">Standard Generalized Markup Language (SGML)</a></em> which was already in common use at CERN, mostly by just adding the <code>&lt;a&gt;</code> tag for hypertext links.</p>
<p>In the early 1990s, HTML was pretty limited. In fact it didn’t include  <code>&lt;img&gt;</code> tags, and its design capabilities were sub-par even when compared to even the clunky word processors of the day. So web pages of the time at best looked like poorly formatted text files.</p>
<p>What the Web did offer, though, was the seemingly magical ability to click a link on one web page and instantly (ok, quickly) load another web page created by anyone else located anywhere in the world.</p>
<p>In 1990, this was big stuff!</p>
<h2 data-sanitized-id='html"1"-and-html+-the-wild-frontier-(1991---1995)'>HTML“1” &amp; HTML+: The Wild Frontier (1991 – 1995)</h2>
<p>The first public mention of HTML was an informal CERN document in 1991, listing just 18 tags. There are now around 140. The Web in those early years was just an academic curiosity used by Universities… well, used by tech geeks at a few universities. The HTML tags were so informal that they were really defined more by whatever the person who wrote the Web browser code wanted them to be and they could (and did) just make up their own.</p>
<p>Although there were a few web browsers available, there was one that rose to popularity amongst the tech geeks of the time.  <em><a href="https://en.wikipedia.org/wiki/Mosaic_(web_browser)">Mosaic</a></em>, released in 1993, was co-created by a (at the time) idealistic and starry eyed student by the name of <a href="https://en.wikipedia.org/wiki/Marc_Andreessen">Marc Andreessen</a> atttending the University of Illinois at Urburna-Champaign. Marc decided it was such a good idea that, in 1994, he released <em><a href="https://en.wikipedia.org/wiki/Netscape_Navigator">Netscape Navigator</a></em> as a consumer product. Free from academic constrains, Netscape could add features and functionality to appeal to non-academics as well as add whatever tags they felt appropriate. These tags were referred to as HTML+.</p>
<p>This freedom led to a lot of growth in what the Web could do with tags for greater styling (remember, CSS is still several years off) and structure. Added during this time were tags we take for granted like tables, forms, and inline images.</p>
<p>This freedom also led to a few really terrible tags, for example Netscape introduced <code>&lt;blink&gt;</code>, which, in case you can’t tell, caused the enclosed content to blink on and off. No, you couldn’t control the speed. No, you couldn’t make it stop. The text just blinked on and off… on and off… on and off… and would keep doing that until you left the page.</p>
<figure data-sanitized-class="u-mb">
<picture>
	<source media="(prefers-reduced-motion: no-preference)">
	<img src="https://htmhell.dev/adventcalendar/2023/5/images/blink_main.jpg" alt="The blink tag blinks on and off, on and off, on and off… to infinity and beyond. Probably best if you can’t see it. Its’ really annoying." loading="lazy">
</picture>
<figcaption>WHATEVER YOU DO, DON’T BLINK!</figcaption>
</figure>
<p>Unfortunately, this also meant that if you used tags recognized by one browser there was no guarantee they would be implemented or even included by another. This lead to problems when your web page didn’t display the way you intended. Fortunately, HTML is a very forgiving language, and if the browser didn’t recognize a tag it was supposed to just ignore it completely but should still display the content as if there was no tag. Still, imagine if you had set up a data table for your web page. If the user’s browser didn’t recognize the <code>&lt;table&gt;</code> tag, all that information becomes an unintelligible mess.</p>
<p>Another problem on the horizon was that there was no guarantee that the different browser makers would agree on what each tag specifically did or even what to call them. One browser might display a <code>&lt;p&gt;</code> with the first line indented, while another could require you to add an extra <code>&lt;p&gt;</code> or  <code>&lt;br&gt;&lt;br&gt;</code> to add a space between paragraphs. Interestingly, what we did at the time out of necessity — no indent and an extra space between paragraphs — has became the editorial norm we still use today.</p>
<p>There was obviously a need for some kind of standard for HTML if the Web was going to grow out of its infancy.</p>
<p>In 1994, Tim started The World Wide Web Consortium, at first to create standards for HTML that could be agreed upon and used by anyone creating browsers and websites.</p>
<p>Netscape would rule the early web until Microsoft, seeing the threat to their desktop market, ignited the bitter struggle known to legend as <em>The Browser Wars</em>.</p>
<figure data-sanitized-class="u-mb">
<img src="https://htmhell.dev/adventcalendar/2023/5/images/NSvIE.jpg" alt="Netscape VS Microsoft Internet explorer buttons next to each other. The Netscape button reads “Netscape Now! 3.0” with the Netscape Logo. The IE button reads “Free Microsoft Internet Explorer” With the IE logo." loading="lazy">
<figcaption>These buttons were ubiquitous in the 1990s World Wide Web, often accompanied a message reading “Built for Netscape” or “Built for Internet Explorer”</figcaption>
</figure>
<div data-sanitized-class="highlight u-mb">
<h3 data-sanitized-id="my-own-private-htmhell-slammin-ps">My Own Private HTMHell: Slammin’ Ps</h3>
<p>I was first introduced to the Web and HTML in the fall of 1994, when I started at Rensselaer Polytechnic Institute (RPI) for my Masters in Communication. I’d heard some rumbling about this new fangled Web thingy earlier in the year while working computer tech at another University, but I had my Gopher and that was good enough for me.</p>
<p>At RPI, I fell in with a rowdy crowd of Communication PhD and Master students, one of whom had co-authored the first book about the web, <em><a href="https://www.amazon.com/exec/obidos/ASIN/0672306174">The World Wide Web Unleashed</a></em>.</p>
<p><a href="https://www.linkedin.com/in/johndecember/">John December</a>, for that was his name, had also started what was, arguably, the first online only magazine, called <em><a href="https://johndecember.com/cmc/mag/archive/">Computer Mediated Communications</a></em>, a few months before I got there. There was no real design to it beyond the basic HTML, so he brought me in to be the first designer.</p>
<p>At the time, being able to display images was still pretty new, and background images could only be applied to the web pages entire background and had to tile vertically and horizontally without stopping. Good enough to add a bit of color or texture, but not much else.</p>
<p>To create web pages required you to code them using a command line prompt in a terminal. I still get hives at the very mention of using a command line, but I was determined to learn HTML. So, despite my trepidation, I tucked into command line editing.</p>
<p>To code and write the pages you had to go back and forth between code mode and text mode. In code mode you could add tags but not text. In text mode you could add text but any code would be treated as text. Switching between these modes could take up to 30 seconds to load each time.</p>
<p>If you forgot to add the <code>&lt;p&gt;</code> in code mode or thoughtlessly added them in text mode (which happened painfully often), you had to go back, delete all of the <code>&lt;p&gt;</code>s in text mode and reinsert them in code mode which could take a lot of time as the system lumbered between modes — you added the tag and then switched back to go to the next spot to add the next tag. I spent many hours doing that at first.</p>
<p>To speed production, we would set up our basic web page code structure and then simply add a bunch of <code>&lt;p&gt;</code> tags (there wasn’t a close paragraph tag at the time) and start writing an article, skipping over the next <code>&lt;p&gt;</code> to start a new paragraph.</p>
<p>We referred to this process as “Slammin’ P’s”.</p>
</div>
<h2 data-sanitized-id="html2-the-standards-rise-(1995---1997)">HTML2: The Standards Rise (1995 – 1997)</h2>
<p>Realizing that all of these different browsers needed to agree on exactly what tags were available in HTML and at least a rough guideline on how they should present the content, Tim started <a href="https://www.w3.org/" title="The World Wide Web Consortium">The World Wide Web Consortium</a>.</p>
<p>The W3C would become the recognized body for setting Web technology standards, including (amongst many others) HTML, CSS, SVG, XML, and Web Accessibility. The one exception to this is JavaScript, which is controlled by <a href="https://www.ecma-international.org/">ECMA International</a>.</p>
<p>HTML2 attempted to make order out of the chaos that HTML had quickly  become. Although not introducing any particularly exciting features, it codified the existing features to give everyone a level playing field to work on.</p>
<p>I just want to reiterate that at this point in the Web, CSS (which today does the lion’s share of actual visual design on the Web) and JavaScript (which does the lion’s share of interactivity on the web) were both new technologies that even if implemented had a lot of cross-browser inconsistencies. JavaScript caught on fast, but it would be the turn of the century before CSS would become the default way to add a visual layer to HTML code.</p>
<div data-sanitized-class="highlight u-mb">
<h3 data-sanitized-id="my-own-private-htmhell-color-me-unimpressed">My Own Private HTMHell: Color Me unimpressed</h3>
<p>In the 1990s most color monitors were limited to just 216 colors. Let me make this clear, since we live in a world of monitors that can display millions of colors: A single pixel on the overwhelming majority of computer monitors in the 1990s could only display one of 216 colors at a time. This limited the color palette for web designs to what became referred to as the <em>Web Safe Colors</em>, which, today, we call just <a href="https://en.wikipedia.org/wiki/Web_colors">Web Colors</a>. Although other colors could be simulated by placing two different colors together and letting the viewers eye blend them (like a <a href="https://en.wikipedia.org/wiki/Pointillism">pointillism</a> painting), the effect was never satisfying, and drop shadows and gradients were never smooth. <a href="https://archive.org/details/coloringwebgraph0000wein/mode/2up">Entire books</a> were devoted to using Browser Safe Colors most effectively. I even kept a poster on my office</p>
<p>When I would try to sneak a drop shadow into a design (I had a high-end monitor that could display thousands of colors!), my creative director would (metaphorically) slap me on the wrist and tell me to cut it out.</p>
<figure data-sanitized-class="u-mb">
<img src="https://htmhell.dev/adventcalendar/2023/5/images/WebmasterPalette.jpg" alt="Poster of the 216-Color Webmaster’s Palette with RGB values" loading="lazy">
<figcaption>In my day we only had 216 colors to design with and we liked it!</figcaption>
</figure>
</div>
<h2 data-sanitized-id="html32-short-and-sweet-(1997)">HTML3.2: Short and Sweet (1997)</h2>
<p>Although it didn’t last long as a standard, HTML3, brought the standard fully under the control of the W3C.  Despite working on it for almost two years, HTML 3.0 was never released as a standard. According to the W3C, the difference between HTML 2.0 and HTML 3.0 was, “so large that standardization and deployment of the whole proposal proved unwieldy” and was eventually dropped.</p>
<p>Instead of starting from scratch, the W3C worked to refine the 2.0 standard, releasing  HTML 3.2 (code named <em>wilbur</em> for reasons no one is sure of) in January of 1997 HTML4 was released a little less than twelve months later in December.</p>
<p>By 1997, the Web was beginning to take off as a commercial platform. HTML 3.2 standardized features we take for granted today like tables, text flow around images, and inputs. Crucially, HTML 3.2 was backwards compatible with HTML 2, meaning that older Web pages could be displayed, something that still holds true to today.</p>
<p>Although some tags were <a href="https://en.wikipedia.org/wiki/Deprecation?wprov=sfti1#">depreciated</a> — most notably <code>&lt;blink&gt;</code> and <code>&lt;marque&gt;</code> to the celebration of many — it secured HTML’s future as a stable standard that could be relied on. In fact, the one serious attempt to move away from the principle of backwards compatibility ended in time waisting disgrace. But we’ll talk more about XHTML2 in a bit.</p>
<p>Although short lived, the <a href="https://www.w3.org/press-releases/1997/html32-rec/">HTML 3.2 recommendation</a> set the stage for keeping the standard free and open, so that no one company could monopolize or privatize it. Instead, the W3C the took public input and invited experts and company representatives  to help guide its development and approve the final recommendations, another principal the W3C still follows.</p>
<p>Sometime around 1996–97, the Web began to quickly gain in popularity as more and more people saw its usefulness. There were publications moving online making news instant, online stores that could undersell their brick-and-mortar competitors (thanks Amazon), and much improved web search capabilities (thanks Google). Oh, and there were apparently some photographs of scantily clad men and women… ummm… exercising together.</p>
<p>This was a time known as the Dot-com Boom.</p>
<div data-sanitized-class="highlight u-mb">
<h3 data-sanitized-id="my-own-private-htmhell-on-the-table">My Own Private HTMHell: On the Table</h3>
<p>Before CSS introduced the float element, and long before the flex and grid standards, we used tables to create web page layouts. The <code>&lt;table&gt;</code> tag was not conceived for this purpose (it was for presenting tabular information and data) but web designers quickly realized they could repurpose it to create columns and rows for <a href="https://en.wikipedia.org/wiki/Grid_(graphic_design)">design grids.</a> This allowed us a lot more flexibility and spurred a lot of design innovation.</p>
<p>But tables were far from perfect. Although you could turn off the table cell borders and collapse gaps between them, many people didn’t. So Web design from that time is often typified by clunky ridged gray borders and pixelated background images.</p>
</div>
<h2 data-sanitized-id="html4-boom-goes-the-web-(1997---2014)">HTML4: Boom Goes the Web (1997 – 2014)</h2>
<p>It was the best of times, it was the worst of times. The HTML4 standard was released and JavaScript and CSS, despite browser inconsistencies, were being adapted to create better designs for a more dynamic Web.</p>
<p>HTML4 improved tables and forms, introduced support for CSS and JavaScript, allowed better accessibility features and internationalization, you could embed external content with the <code>&lt;iframe&gt;</code> tag (the much maligned <code>&lt;frame&gt;</code> tag was deprecated), and plugins like Flash could be added using the <code>&lt;embed&gt;</code> tag.</p>
<p>A gold rush began, with start-up companies springing up like weeds. This period was know as the Dot-Com Boom. Everybody and their brother had an idea for how to make money out of the Web, and they all needed someone who knew HTML. Jobs quickly flourished for Information Architects, Visual Designers, and Programmers. The good times of free gourmet meals at work, video arcade break rooms, and corporate parties on cruise lines seemed like it would never end.</p>
<p>At the same time, two different Web browsers were vying for the hearts and minds of the Web viewing public. This was a time known to Web lore as “<a href="https://en.wikipedia.org/wiki/Browser_wars" title="The Browser Wars">The Browser Wars</a>” as Microsoft Internet Explorer and Netscape Navigator battled it out.  Maybe we should really call this The First Browser War or The Great Browser War or Browser War I or maybe even WWWWAR I. As we shall see, another browser war was less than ten years away and another one may be going on now.</p>
<p>The Dot-com boom turned out to be a bubble and the bubble began to burst in late 2000. I will not dwell too long on this sad chapter in Web history, only to say that it had a profound effect on the burgeoning Web professionals of the day. Many were unceremoniously dumped from their first job  — their dream job  — crying over stock options that went from millions to pennies on the dollar all while watching the entire industry seemed to be imploding around them.</p>
<p>That said, the Dot-com bust cleared the way for a rethinking of how the web would work. Just a few years later, there was an explosion of new sites that were finally realizing the promise of the Web. Although Web 2.0 is often thought of as starting in the early 2000s, the power that drove it had been around since the mid-1990s when Cascading Style Sheets and Javascript (the duo technologies often referred to together at the time as DHTML or Dynamic HTML) became standards adopted by Web browsers.</p>
<p>What had held the development back was the technical debt of how the web was started and inconsistencies in how browsers displayed the Core Web Standards (HTML, CSS, JavaScript). What would look great in Netscape might fall apart in Internet Explorer, so we developed a lot of work arounds and <a href="https://en.wikipedia.org/wiki/Kludge">kludges</a> or just limited designs for cross-browser compatibility.</p>
<p>For better or worse, by 2001 Microsoft came out on top, and Web designers could begin to rely on a consistent, if lack luster in its standards adherence, Web browser to create for. Web2.0 could now actually take off.</p>
<p>After that, things simmered down for a bit in a period I refer to as “Pax Web”, and Flash, although not an open standard like HTML, was quickly adapted to bring more video and interactive content to the Web. Flash was easy enough for designers to use without having to learn a lot of code, and allowed for dynamic and highly interactive designs, something we struggle to recreate on the Web even today <a href="https://www.howtogeek.com/805605/this-is-how-steve-jobs-killed-adobe-flash/">after Flash’s demise at the hands of the Apple iPhone</a>.</p>
<div data-sanitized-class="highlight u-mb">
<h3 data-sanitized-id="my-own-htmhell-internet-explorer-6">My Own HTMHell: Internet Explorer 6</h3>
<p>Ask any Web designer or developer working in the 2000s what it was that woke them up screaming in the middle of the night in panic and rage, the answer will likely be Internet Explorer 6. Launched in 2001 and preinstalled on every Windows computer as the default browser, it became <em>the Internet</em> to many people.</p>
<p>While talking to a class of fourth graders in the mid-2000s about the Web, I wanted to use their class computer to show some examples and asked the teacher if they had a Web browser. Her response was, “No, we just have the Internet,” and pointed to the Internet Explorer Icon.</p>
<p>Despite being the window to the web for billions (at its height, IE6 had something  like around 80% of the world browser market), it had spotty, incomplete, inconsistent and buggy Web standards implementations. This made the job of creating Web sites difficult at the best of times, especially if you wanted to stick to standards and maintain cross-browser support.</p>
<p>I spent many, many, MANY hours during that time adjusting and re-adjusting my HTML, CSS, and JavaScript, using arcane kludges — like playing a sadistic whack-a-mole where whacking one guarantees another five popping up —  and often just having to settle for the lowest common denominator to get what I wanted to display the way I wanted it to display the same way on every browser.</p>
</div>
<h2 data-sanitized-id="xhtml1-strict-machine-(2000---2008)">XHTML1: Strict Machine (2000 – 2008)</h2>
<p>Although HTML4 solidified the Web, there were those who thought it too loose-goose to be considered a real solution for data mark up. HTML was very forgiving. It didn’t <em>require</em> that you follow proper syntax and anything the browser didn’t understand it would just throw out. That is browsers <em>except</em> IE6, of course, which would just choke (more HTMHell).</p>
<p>In 2000, the W3C released <a href="https://www.w3.org/TR/xhtml1/" title="XHTML™ 1.0 The Extensible HyperText Markup Language (Second Edition)">XHTML 1.0</a> as a reformulation of HTML using XML syntax. Extensible Markup Language (XML) had been around for a while as a markup language used to structure and organize data. The W3C decided to establish a competing standard to provide easier integration with XML code. XHTML aimed to combine the best of HTML and XML, providing stricter rules for markup and enabling interoperability with XML-based technologies.</p>
<p>XHTML was stricter than simple HTML, meaning that syntax couldn’t be fudged, but creating a page using XHTML was compatible with and little different than creating one in HTML4. However, it did force developers to be more semantic, consistent, and accessible with their code, something still important today.</p>
<h2 data-sanitized-id="xhtml2-look-back-in-anger-(doa)">XHTML2: Look Back in Anger (DOA)</h2>
<p>The idea behind XHTML2 was bold, brave, and completely ineffective. Rather than simply evolve HTML and XHTML, <a href="https://www.w3.org/TR/2010/NOTE-xhtml2-20101216/" title="XHTML™ 2.0">XHTML2</a> sought to start over and create a stricter and more modular version the Web markup language. It was heavily based on XML and eliminated all presentation elements in favor of CSS. The idea was to force the complete separation of structure from presentation thus allowing for the code to be device independent.</p>
<p>There was just one problem: XHTML2 would not be backwards compatible.</p>
<p>HTML had, since its beginning, been backwards compatible, meaning that older Web pages could display even in browsers using newer HTML standards. XHTML2 would have quite literally broken the Web. In addition, the new language was far more complex, and stricter enforcement of syntax and structure made creating Web pages much more difficult without a programming background.</p>
<p>Development of<a href="https://hsivonen.fi/xhtml2-html5-q-and-a/" title="An Unofficial Q&amp;A about the Discontinuation of the XHTML2 WG"> XHTML2 was eventually abandoned in 2009</a> as interest in a more promising  standard was catching on.</p>
<h2 data-sanitized-id="html5-a-new-hope-(2014---now)">HTML5: A New Hope (2014 – Now)</h2>
<figure data-sanitized-class="u-mb">
<img src="https://htmhell.dev/adventcalendar/2023/5/images/front-end-stack.png" alt=" Logos for HTML5 (red), JS (yellow), and CSS3 (blue)" loading="lazy">
<figcaption>Core Web Technologies: HTML 5, JavaScript, and CSS 3</figcaption>
</figure>

<p>Although not officially released as a standard until 2014, HTML5 had already become the de facto standard in Web browsers starting around 2010. Coupled with a developing CSS3 and a mature JavaScript, the Web was poised for a new phase. The rise of mobile devices being used to surf the Web meant that we had to start thinking more and more about how Web pages would look on small screens. Rather than creating a site for desktop/laptop and one for mobile, CSS could be used to change the layout of the HTML in response to the screen size. This became most commonly known as <em><a href="https://alistapart.com/article/responsive-web-design/" title="Responsive Web Design
by Ethan Marcotte">Responsive Web Design</a></em>.</p>
<p>HTML5 made an accessible and semantic Web its priority. Semantic HTML code simply means that it is well structured and that every item on the page has a clearly defined purpose. So <a href="https://dev.opera.com/articles/new-structural-elements-in-html5/" title="New Structural Elements in HTML5">structural HTML tags</a> such as <code>&lt;nav&gt;</code>, <code>&lt;section&gt;</code>, <code>article</code>, and <code>&lt;figure&gt;</code> were added. New input types were added as well, so that the data could be more easily differentiated (if only developers would use them properly!).</p>
<p>While XHTML2 was discontinued, the idea of combining HTML and XML syntax continued to be explored leading to XHTML5. Like XHTML1, XHTML5 allows developers to write web pages using the stricter rules of XML while still benefiting from the features and compatibility of HTML5.</p>
<p>Because Internet Explorer was languishing without updating to modern standards (IE 7 improvements came too little too late in 2006 and took years to replace IE6), Chrome was at the forefront of implementing HTML5 and CSS3. The upstart browser quickly caught on and rapidly grew in popularity.</p>
<p>The beginning of the end of WWWWar II came in <a href="https://gs.statcounter.com/browser-market-share/desktop/worldwide/#monthly-200901-201612" title="Desktop Browser Market Share Worldwide
Jan 2009 - Dec 2016">2012 when Chrome unseated Internet Explorer as the top browser world wide</a> and IE never recovered after that. Although IE was still a major player for several years, by 2015, seeing that Internet Explorer was too tech debt laden to compete, Microsoft launched a completely new Web browser: Edge. Internet Explorer would finally be discontinued in 2022, but there are signs that Microsoft is gearing up for another fight.</p>
<figure data-sanitized-class="u-mb">
<img src="https://htmhell.dev/adventcalendar/2023/5/images/StatCounter-browser-ww-monthly-200901-201612.png" alt="Chart from StatCounter showing the desktop browser market share worldwide from January 2009 through December 2016. Google Chrome surpasses Microsoft Internet Explorer market share in mid-2012. " loading="lazy">
<figcaption>In 2012, Google Chrome passed Microsoft Internet Explorer as the most popular web browser in the world. The WWW would never be the same.</figcaption>
</figure>
<p>Two other huge advances in Web design happened around 2009. First was that CSS Level 3 had become a de facto — if not finalized — standard. This revised and expanded styling standard was quickly adapted by browser makers eager to push their browsers as the most standard compliant. Almost as importantly, and long overdue, was the ability to use downloadable font files to use virtually any typeface in Web designs. This push, led the way by Safari was quickly introduced into Chrome and finally a few years later begrudgingly by Internet Explorer.</p>
<p>Additionally, since Apple refused to support Flash on its extremely popular iPhone, Flash quickly fell out of favor as a way of delivering video and interactive content. Fortunately, HTML5 included new (and old) standards for embedding video, and <a href="https://en.wikipedia.org/wiki/SVG">Scalable Vector Graphics (SVG)</a>, an old Web standard many had all but given up on, saw a resurgence in popularity to replace Flash’s vector capabilities.</p>
<p>The explosion of typography on the web, the new capabilities in CSS3, and the resuscitation of SVG led to a renaissance in Web design in the 2010s. Designers, having to move away from Flash so that they could support mobile devices, were now also expected to create designs that would work just as well on devices from a few hundred to a few thousand pixels wide. In effect, we were looking at an entirely new Web by the end of the 2000s.</p>
<p>When an evolutionary change like this happened in the early-2000s, we called it Web 2.0. I would argue that the period from roughly 2009 – 2016 should be referred to as Web 3.0. Despite the terms Web3 and Web 3.0 being in common usage today to describe a decentralization of the Web (the term was first used sometime in the mid to late 2010s, but is still little more than a marketing buzzword) we missed an entire phase where the Web evolved off the desktop and became mobile, leading to a need to radically rethink our approach to Web design as “<a href="https://bradfrost.com/blog/post/mobile-first-responsive-web-design/">Mobile First</a>” strategies developed.</p>
<h2 data-sanitized-id="the-future-of-html?">The Future of HTML?</h2>
<p>So, where are we now? Are we already living in a Web3 world already? Maybe.</p>
<p>We hear a lot of discussion about the <a href="https://3327.io/user-decentric-design-in-web3/" title="USER (DE)CENTRIC DESIGN IN WEB3">next web phase being “Web 3”</a> but many others are <a href="https://web3isgoinggreat.com/" title="Web3 is Going Just Great">less than thrilled by the prospect</a>. There is little doubt that user’s control of their data has eroded to the point of non-existence in the last thirty years. At the heart of Web3 (beyond cryptocurrency and blockchain) is a desire to bring back a decentralized Web with greater individual user control. While this is unlikely to affect the HTML standard, it may radically alter the way we use it.</p>
<p>To complicate matters further, we appear to be <a href="https://interestingengineering.com/culture/is-microsoft-launching-a-new-browser-war" title="Is Microsoft Launching a New Browser War?">heading into a new Browser War</a>, with Microsoft recently launching several shots across the bow of Google—oh, excuse me, I mean “Alphabet”—leveraging Windows OS to push users towards a newly revitalized Edge Browser and away from Chrome.</p>
<p>Despite all of this, HTML5 is not going anywhere soon. Unlike previous versions, HTML5 is modular. That is, rather than rereleasing the entire standard as a single document, new capabilities are added to the existing standard. Older standards are rarely, if ever, changed.</p>
<p>That said, the W3Cs <a href="https://open-ui.org/" title="Open UI">Open UI Group</a> is addressing a long standing problem with HTML: user interface control appearance and behavior.</p>
<blockquote>Most complex web projects today need far more than what HTML5 form and UI controls provide.</blockquote>
<p>Currently, web developers will use a lot of CSS and Javascript to add capabilities that would be better handled by built in HTML components, but there are only limited ways to style these elements and none for how they function without resorting to scripting. Although still in its infancy, Open UIs goal is to web developers more control to style and extend web UI components without changing the underlying HTML code.</p>
<p>In a little more than 30 years, the Web has gone from being a Wild Frontier to becoming an essential utility humanity relies on to communicate, and HTML will always be the core of the Web.</p>


guid: https://htmhell.dev/adventcalendar/2023/6/
title: Web Components FTW!
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/6/
hash: H��-�)�ڿ��*_�
attributes: {"enclosures":[]}
content:
by Chris Ferdinandi<br><p>Web Components are a collection of technologies that you can use to create reusable custom elements, with built-in interactivity, automatically scoped (or encapsulated) from the rest of your code.</p>
<p>They have a wide range of features and functionality (some good, some bad, some ugly), but today, we're going to look at how to create your first Web Component using the most cliche of examples: the counter button.</p>
<p>We'll learn how this...</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>Automagically becomes somethings like this under-the-hood...</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>button</span> <span data-sanitized-class="token special-attr"><span data-sanitized-class="token attr-name">onclick</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span><span data-sanitized-class="token value javascript language-javascript"><span data-sanitized-class="token function">increase</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span></span><span data-sanitized-class="token punctuation">"</span></span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Clicked 0 Times<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>script</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token script"><span data-sanitized-class="token language-javascript"><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">let</span> count <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">function</span> <span data-sanitized-class="token function">increase</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">event</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">		count<span data-sanitized-class="token operator">++</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">		event<span data-sanitized-class="token punctuation">.</span>target<span data-sanitized-class="token punctuation">.</span>textContent <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">Clicked </span><span data-sanitized-class="token interpolation"><span data-sanitized-class="token interpolation-punctuation punctuation">${</span>count<span data-sanitized-class="token interpolation-punctuation punctuation">}</span></span><span data-sanitized-class="token string"> Times</span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br></span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>script</span><span data-sanitized-class="token punctuation">&gt;</span></span></code></pre>
<p>Let's dig in!</p>
<h2 data-sanitized-id="creating-a-custom-element">Creating a custom element</h2>
<p>First, we'll create a custom element.</p>
<p>You can name it anything you want, but it must include at least one dash (<code>-</code>). Single-word web components are not allowed (those are reserved for native elements).</p>
<p>Let's uncreatively name ours <code>counter-button</code>.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>Next, we need to register our custom element with JavaScript.</p>
<p>We do that with <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define">the <code>customElements.define()</code> method</a>. We'll pass in the name of our custom element as the first argument, and a Class that extends <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">the <code>HTMLElement</code> API</a> as our second.</p>
<p>When our page loads, this will automatically find any custom elements with the name we provided and instantiate a new instance of our Class on them.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line">customElements<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">define</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">class</span> <span data-sanitized-class="token class-name">extends</span> HTMLElement <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Stuff will happen here!</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>Inside the class, we'll add <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor">the <code>constructor()</code> method</a>. This runs when a new instance of the Class is created.</p>
<p>In it, we'll user <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super">the <code>super()</code> operator</a> to make sure we have access to the parent class (in this case, the <code>HTMLElement</code> object) properties.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line">customElements<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">define</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">class</span> <span data-sanitized-class="token class-name">extends</span> HTMLElement <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br>	<span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line">	 * The class constructor object</span><br>	 */</span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token function">constructor</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// Always call super first in constructor</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">super</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>Next, we'll add a property to track the number of clicks: <code>this.count</code>. You can think of this like Component state in your favorite JS library or framework.</p>
<p>(<em>In a JavaScript Class, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this">the <code>this</code> keyword</a> refers to the current instance of the Class.</em>)</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line">customElements<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">define</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">class</span> <span data-sanitized-class="token class-name">extends</span> HTMLElement <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br>	<span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line">	 * The class constructor object</span><br>	 */</span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token function">constructor</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// Always call super first in constructor</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">super</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// Track the count</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>Finally, we'll use the <code>innerHTML</code> property to render a <code>button</code> into the custom element (<code>this</code>), with our <code>count</code> state displayed as part of the text.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line">customElements<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">define</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">class</span> <span data-sanitized-class="token class-name">extends</span> HTMLElement <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br>	<span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line">	 * The class constructor object</span><br>	 */</span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token function">constructor</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// Always call super first in constructor</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">super</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// Track the count</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// Render HTML</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>innerHTML <span data-sanitized-class="token operator">=</span></span><br><span data-sanitized-class="highlight-line">			<span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">&lt;button&gt;Clicked </span><span data-sanitized-class="token interpolation"><span data-sanitized-class="token interpolation-punctuation punctuation">${</span><span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count<span data-sanitized-class="token interpolation-punctuation punctuation">}</span></span><span data-sanitized-class="token string"> Times&lt;/button&gt;</span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>Now, a <code>&lt;button&gt;</code> is automatically rendered into the UI wherever we include our custom element. It looks like this in the HTML.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span>Clicked 0 Times<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<h2 data-sanitized-id="adding-interactivity">Adding interactivity</h2>
<p>Web Components have <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#using_the_lifecycle_callbacks">"lifecycle functions"</a> that run when various things happen.</p>
<ul>
<li>The <code>constructor()</code> method is run when the element is created, before its injected into the UI.</li>
<li>The <code>connectedCallback()</code> method is run when the element is injected into the DOM, and again whenever it's moved or appended elsewhere.</li>
<li>The <code>disconnectedCallback()</code> method is run whenever the element is removed from the DOM.</li>
</ul>
<p>When the <code>&lt;button&gt;</code> is clicked, we want to increase our <code>count</code> by <code>1</code> and update the UI.</p>
<p>We don't need to listen for clicks until the element is loaded into the DOM, so we'll use <code>connectedCallback()</code> method to add a <code>click</code> event listener.</p>
<p>Inside our Web Component Class, <code>this</code> is the custom element (<code>&lt;counter-button&gt;</code>).</p>
<p>We'll attach a <code>click</code> event listener to <code>this</code>. Inside the event handler function, we'll increase our <code>count</code> by <code>1</code>, then render new text into the <code>firstElementChild</code>, the <code>&lt;button&gt;</code> element.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line">customElements<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">define</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">class</span> <span data-sanitized-class="token class-name">extends</span> HTMLElement <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br>	<span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line">	 * The class constructor object</span><br>	 */</span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token function">constructor</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br>	<span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line">	 * Runs each time the element is appended to the DOM</span><br>	 */</span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token function">connectedCallback</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">addEventListener</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'click'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">function</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">			<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count<span data-sanitized-class="token operator">++</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">			<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>firstElementChild<span data-sanitized-class="token punctuation">.</span>textContent <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">Clicked </span><span data-sanitized-class="token interpolation"><span data-sanitized-class="token interpolation-punctuation punctuation">${</span><span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count<span data-sanitized-class="token interpolation-punctuation punctuation">}</span></span><span data-sanitized-class="token string"> Times</span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>Now, whenever the button is clicked, the <code>count</code> and UI are automatically updated.</p>
<p>If you have multiple <code>&lt;counter-button&gt;</code> elements on a page, each one is self-contained and encapsulated. Updating the <code>count</code> on one doesn't affect the UI of the other.</p>
<p><a href="https://codepen.io/cferdinandi/pen/GRzgpEK">Here's a demo.</a></p>
<h2 data-sanitized-id="detecting-attribute-changes">Detecting attribute changes</h2>
<p>The web component lifecycle includes an additional function, <code>attributeChangedCallback()</code>, that runs when attributes on a custom element are added, removed, or changed in value.</p>
<p>You can use it to detect attribute changes and run code in response.</p>
<p>For example, imagine if you wanted to let third-party JavaScript update the <code>count</code> on a <code>&lt;counter-button&gt;</code> by setting a <code>count</code> attribute, like this...</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">let</span> counter <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">counter<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">setAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'count'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token number">42</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>First, we need to create a static <em>getter</em> method named <code>observedAttributes()</code>.</p>
<p>This function needs to return an array of attributes to watch. For performance reasons, only attributes listed in this array will be observed by the <code>attributeChangedCallback()</code> function.</p>
<p>We'll return an array with the <code>count</code> attribute.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line"> * Create a list of attributes to observe</span><br> */</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">static</span> <span data-sanitized-class="token keyword">get</span> <span data-sanitized-class="token function">observedAttributes</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">return</span> <span data-sanitized-class="token punctuation">[</span><span data-sanitized-class="token string">'count'</span><span data-sanitized-class="token punctuation">]</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>Next, we'll add an <code>attributeChangedCallback()</code> function.</p>
<p>It accepts three arguments: the <code>name</code> of the attribute that's been changed, its <code>oldValue</code>, and its <code>newValue</code>.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line"> * Runs when the value of an attribute is changed on the component</span><br><span data-sanitized-class="highlight-line"> * @param  {String} name     The attribute name</span><br><span data-sanitized-class="highlight-line"> * @param  {String} oldValue The old attribute value</span><br><span data-sanitized-class="highlight-line"> * @param  {String} newValue The new attribute value</span><br> */</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">attributeChangedCallback</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">name<span data-sanitized-class="token punctuation">,</span> oldValue<span data-sanitized-class="token punctuation">,</span> newValue</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">	console<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">log</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'changed'</span><span data-sanitized-class="token punctuation">,</span> name<span data-sanitized-class="token punctuation">,</span> oldValue<span data-sanitized-class="token punctuation">,</span> newValue<span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>At this point, if we set or update the <code>count</code> attribute on our <code>counter-button</code> element, the <code>attributeChangedCallback()</code> will log some stuff into the console.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">let</span> counter <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Nothing will happen here, because we're not watching this attribute</span></span><br><span data-sanitized-class="highlight-line">counter<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">setAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'hello'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token string">'you'</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// logs "changed" "count" null 42</span></span><br><span data-sanitized-class="highlight-line">counter<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">setAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'count'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token number">42</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>If we were observing more than one attribute, we would want to first check what the <code>name</code> was before doing anything in the <code>attributeChangedCallback()</code> function.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line"> * Runs when the value of an attribute is changed on the component</span><br><span data-sanitized-class="highlight-line"> * @param  {String} name     The attribute name</span><br><span data-sanitized-class="highlight-line"> * @param  {String} oldValue The old attribute value</span><br><span data-sanitized-class="highlight-line"> * @param  {String} newValue The new attribute value</span><br> */</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">attributeChangedCallback</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">name<span data-sanitized-class="token punctuation">,</span> oldValue<span data-sanitized-class="token punctuation">,</span> newValue</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// If the [count] attribute</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">if</span> <span data-sanitized-class="token punctuation">(</span>name <span data-sanitized-class="token operator">===</span> <span data-sanitized-class="token string">'count'</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>For this web component, though, the function only runs for the <code>count</code> attribute, so we don't have to check the <code>name</code>, nor do we have to worry about the <code>oldValue</code>.</p>
<p>First, we'll pass the <code>newValue</code> into the <code>parseFloat()</code> method to make sure its to a number. We'll also check if it's <em>Not a Number</em> (<code>isNaN()</code>).</p>
<p>Then, we'll update the <code>count</code> property, and render the new <code>count</code> into the <code>firstElementChild</code> (the <code>&lt;button&gt;</code>).</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line"> * Runs when the value of an attribute is changed on the component</span><br><span data-sanitized-class="highlight-line"> * @param  {String} name     The attribute name</span><br><span data-sanitized-class="highlight-line"> * @param  {String} oldValue The old attribute value</span><br><span data-sanitized-class="highlight-line"> * @param  {String} newValue The new attribute value</span><br> */</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">attributeChangedCallback</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">name<span data-sanitized-class="token punctuation">,</span> oldValue<span data-sanitized-class="token punctuation">,</span> newValue</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Make sure the new value is a number</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">let</span> num <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token function">parseFloat</span><span data-sanitized-class="token punctuation">(</span>newValue<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">if</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token function">isNaN</span><span data-sanitized-class="token punctuation">(</span>num<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token keyword">return</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Update the count and render the UI</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count <span data-sanitized-class="token operator">=</span> num<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>firstElementChild<span data-sanitized-class="token punctuation">.</span>textContent <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">Clicked </span><span data-sanitized-class="token interpolation"><span data-sanitized-class="token interpolation-punctuation punctuation">${</span><span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count<span data-sanitized-class="token interpolation-punctuation punctuation">}</span></span><span data-sanitized-class="token string"> Times</span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>Now, whenever the <code>count</code> attribute is set or updated on our custom element, the <code>count</code> and UI are updated to match.</p>
<p><a href="https://codepen.io/cferdinandi/pen/vYbENzB?editors=1111">Here's another demo.</a></p>
<h2 data-sanitized-id="the-shadow-dom">The Shadow DOM</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">The <em>Shadow DOM</em> is a special DOM</a>, whose contents are separate and often isolated from the main DOM.</p>
<p>Web Components can use the Shadow DOM to <em>encapsulate</em> their child elements, and avoid the naming collisions and unintended side effects that sometimes happen when code is used by teams or across projects.</p>
<p>For example, with our current Web Component, someone could (<em>hopefully accidentally!</em>) wipe out all of the content inside the element.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">let</span> counter <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'counter-button'</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">counter<span data-sanitized-class="token punctuation">.</span>innerHTML <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token string">''</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>With the Shadow DOM, content inside the custom element cannot be accessed or modified with outside JavaScript or CSS.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	#shadow-root (closed)</span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span>Clicked 0 Times<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>counter-button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>Inside our constructor, immediately after running the <code>super()</code> method, we can use the <code>Element.attachShadow()</code> method to create a shadow DOM for our web component.</p>
<p>It accepts an object of <code>options</code>. The only one we need to specify is the <code>mode</code>.</p>
<p>If we use <code>open</code> as the value, JavaScript from outside of our web component can access the DOM nodes. With a value of <code>closed</code>, only JavaScript inside the web component itself can access the Shadow DOM.</p>
<p>The <code>Element.attachShadow()</code> method returns to the <code>ShadowRoot</code> object. To easily access it in our methods, we'll assign it to the <code>root</code> property for our instance.</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line"> * The class constructor object</span><br> */</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">constructor</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Always call super first in constructor</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">super</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Creates a shadow root</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>root <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">attachShadow</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">{</span>mode<span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token string">'closed'</span><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>Now, instead of injecting content into the web component itself (<code>this</code>), we'll use the <code>ShadowRoot</code> (<code>this.root</code>).</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="token comment">/**<br><span data-sanitized-class="highlight-line"> * The class constructor object</span><br> */</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">constructor</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Always call super first in constructor</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">super</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Creates a shadow root</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>root <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">attachShadow</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">{</span>mode<span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token string">'closed'</span><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Track the count</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token comment">// Render HTML</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>root<span data-sanitized-class="token punctuation">.</span>innerHTML <span data-sanitized-class="token operator">=</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">&lt;button&gt;Clicked </span><span data-sanitized-class="token interpolation"><span data-sanitized-class="token interpolation-punctuation punctuation">${</span><span data-sanitized-class="token keyword">this</span><span data-sanitized-class="token punctuation">.</span>count<span data-sanitized-class="token interpolation-punctuation punctuation">}</span></span><span data-sanitized-class="token string"> Times&lt;/button&gt;</span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p><a href="https://codepen.io/cferdinandi/pen/wvNBKLJ">Here's a demo of our Web Component with the Shadow DOM.</a></p>
<h2 data-sanitized-id="opinion-do-not-use-the-shadow-dom-by-default">Opinion: do <em>not</em> use the Shadow DOM by default</h2>
<p>While the Shadow DOM protects your code from unintentional side-effects and interactions, but also introduces some of its own challenges.</p>
<p>One of the biggest ones is around styling. Shadow DOM elements do not inherit global styles like elements in the main DOM do. There are a few techniques for styling elements in the Shadow DOM, but they're all a bit clunky and require more work from you as a developer.</p>
<p>There are also some accessibility challenges related to the Shadow DOM. <a href="https://www.matuzo.at/blog/2023/web-components-accessibility-faq/">Manuel has documented many of them here</a>.</p>
<p>Unfortunately, some of Web Components best features (like <code>template</code> and <code>slot</code>) require the Shadow DOM.</p>
<p>But in my opinion, the tradeoffs and challenges with using the Shadow DOM outweigh the benefits it provides in most cases. It has its place, but the <em>Light DOM</em> (ie. the "regular" DOM) should be the default approach in most instances.</p>
<h2 data-sanitized-id="why-use-web-components?">Why use web components?</h2>
<p>You don't <em>need</em> web components to create a counter button... or any component, for that matter. So why use them?</p>
<p>First, they make it a lot more obvious what complex components are in your UI. An element called <code>loading-icon</code> is a lot more obvious than a set of empty nested <code>div</code> elements with a <code>.loading-ring</code> class on it.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">&lt;!-- This is clear --&gt;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>loading-icon</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>loading-icon</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">&lt;!-- This is clunky --&gt;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">class</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>visually-hidden<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">role</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>status<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	Waiting to load...</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">class</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>loading-ring<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>If you're working with a team of developers or creating a design system, web components provide a handful of additional benefits.</p>
<p>First, because the web component generates all of the behind-the-scenes HTML, you don't need to worry about developers forgetting something important.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">&lt;!-- This is the wrong class --&gt;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">class</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>loading-rings<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">&lt;!-- This one is missing a div --&gt;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">class</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>loading-ring<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>If you fix a bug or update your component, you don't have to manually update a bunch of HTML everywhere the component is used. You only need to update the JavaScript.</p>
<p>For example, imagine that the original version of your loading icon component did <em>not</em> include an ARIA live region to announce when the content was loaded to screen readers. You realize the mistake and update the component.</p>
<p>With a traditional HTML component, any developer who uses it will need to update their HTML, JavaScript, and CSS files to include the fix. With web components, they only have to update the JavaScript file.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="token comment">&lt;!-- <br><span data-sanitized-class="highlight-line">	No changes needed</span><br><span data-sanitized-class="highlight-line">	The component handles the HTML for you</span><br>--&gt;</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>loading-icon</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>loading-icon</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">&lt;!-- This is the only change needed --&gt;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>script</span> <span data-sanitized-class="token attr-name">src</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>loading-icon.v2.js<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token script"></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>script</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>To summarize, the benefits of web components include...</p>
<ul>
<li>Clarity and readability</li>
<li>A simpler and less error prone developer experience</li>
<li>Ease of updating components</li>
<li>Scoping and encapsulation</li>
</ul>
<h2 data-sanitized-id="learn-more!">Learn more!</h2>
<p>This article only scratched the surface on Web Components and what they're capable of.</p>
<p>You can <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">learn more over at MDN</a>. I also have <a href="https://leanwebclub.com/">a standalone course and a project-based workshop on Web Components as part of my Lean Web Club</a>.</p>


guid: https://htmhell.dev/adventcalendar/2023/7/
title: Security Headers using &amp;lt;meta&amp;gt;
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/7/
hash: ��~cJF���)= �+W
attributes: {"enclosures":[]}
content:
by Saptak S<br><p>Various <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers">HTTP headers</a> are sent between the user and the server of a website in the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Session">request-response cycle</a>. Some of these HTTP response headers sent by the server to the browser help enhance the security and privacy of the website's users. These sets of headers are often commonly known as <a href="https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html">security headers</a>. These can range anywhere from forcing the browser to always visit your website through <code>https</code> (<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">Strict-Transport-Security</a>) to preventing <a href="https://owasp.org/www-community/attacks/xss/">Cross Site Scripting</a> and <a href="https://owasp.org/www-community/attacks/Clickjacking">Clickjacking</a> attacks on the website's user. You can check how well the security headers are set for your website using <a href="https://securityheaders.com/">securityheaders.com</a>.</p>
<p>These security headers (or any HTTP response headers) are usually set through the server configuration, i.e., nginx config, Apache config, or similar. However, in certain scenarios, like hosting a static website using GitHub Pages, the developers don't have a lot of control over the HTTP response headers. In such scenarios, the ability to add security headers declaratively through HTML itself can be helpful. The <code>&lt;meta&gt;</code> HTML tag helps in setting up two such security headers - <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy">Referrer Policy</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policy</a>.</p>
<h2 data-sanitized-id="referrer-policy-using-lessmeta-namegreater">Referrer Policy using <code>&lt;meta name&gt;</code></h2>
<p>One of the most common ways a <code>&lt;meta&gt;</code> HTML tag is used is with a <code>name</code> and <code>content</code> attribute. The HTML standard allows the <code>&lt;meta&gt;</code> element to have <a href="https://html.spec.whatwg.org/multipage/semantics.html#meta-referrer"><code>name="referrer"</code></a> that gets delivered as a <code>Referrer-Policy</code> header. The <code>content</code> attribute defines the value of the <code>Referrer-Policy</code> header. For example,</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>meta</span> <span data-sanitized-class="token attr-name">name</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>referrer<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">content</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>no-referrer<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>will create the HTTP response header: <code>Referrer-Policy: no-referrer</code>. This will ensure that any visitor of your website, if they click on a link in your website, the <a href="https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.2"><code>Referer</code></a> information of the visitor is not shared with the link. So the visited link will not know which previous URL the user has clicked the link from and helps protect the privacy of the user. There are a few different <a href="https://www.w3.org/TR/referrer-policy/#referrer-policies">Referrer-Policy values</a> that allow varied levels of information to be shared in the <code>Referer</code> header to the navigating link or subresources.</p>
<p>There are a few other ways that HTML allows setting up Referrer-Policy on individual links -</p>
<ul>
<li><code>referrerpolicy</code> attribute</li>
<li><code>rel="noreferrer"</code>.</li>
</ul>
<p>The <code>referrerpolicy</code> attribute can be used like this:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>a</span> <span data-sanitized-class="token attr-name">href</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>http://example.com<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">referrerpolicy</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>origin<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>This means when the user clicks on this particular anchor tag, only the <code>origin</code> information of the current website is sent to <code>https://example.com</code>. The <code>referrerpolicy</code> attribute can also be set on elements like <code>&lt;img&gt;</code> or <code>&lt;iframe&gt;</code> to control their Referrer-Policy.</p>
<p>Adding <code>rel="noreferrer"</code> to an anchor tag ensures that no referrer information of the current webpage is sent to the visiting webpage.</p>
<h2 data-sanitized-id="content-security-policy-using-http-equiv">Content Security Policy using <code>http-equiv</code></h2>
<p>Another way of using <code>&lt;meta&gt;</code> tags is using the <a href="https://html.spec.whatwg.org/multipage/semantics.html#pragma-directives"><code>http-equiv</code></a> attribute instead of the <code>name</code> attribute. The <code>http-equiv</code> attribute is used by servers to create various HTTP response headers. One such header is the <a href="https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy"><code>Content-Security-Policy</code></a>.  Content Security Policy (CSP) is used to determine whether a content being loaded in the page is from a trusted source or not. CSP is versatile in controlling all different kinds of content from which script gets loaded to what a form target can be set to through its various <a href="https://w3c.github.io/webappsec-csp/#csp-directives">directives</a>, and hence protects your users from a wide range of attacks such as clickjacking attacks and cross-site scripting attacks.</p>
<p>The way to add a CSP using <code>http-equiv</code> is:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>meta</span> <span data-sanitized-class="token attr-name">http-equiv</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>Content-Security-Policy<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">content</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>script-src <span data-sanitized-class="token punctuation">'</span>self<span data-sanitized-class="token punctuation">'</span>; form-action <span data-sanitized-class="token punctuation">'</span>none<span data-sanitized-class="token punctuation">'</span><span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>The above line creates an equivalent HTTP response header</p>
<pre data-sanitized-class="language-json"><code data-sanitized-class="language-json"><span data-sanitized-class="highlight-line">Content-Security-Policy<span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token string">"script-src 'self'; form-action 'none';"</span></span></code></pre>
<p>The <code>script-src</code> directive in the above example ensures that URLs loaded in <code>&lt;script&gt;</code> element are from the same origin as the current webpage, and disallows any inline-scripts or script URLs from other websites. The <code>form-action</code> directive ensures that no form submission is allowed from the current webpage. These both help in preventing cross-site scripting attacks.</p>
<p data-sanitized-class="highlight">
<strong>Caution:</strong> One very important thing to note is using this method, <code>report-uri</code>, <code>frame-ancestors</code>, and <code>sandbox</code> <a href="https://w3c.github.io/webappsec-csp/#directives">directives</a> cannot be set for a CSP. They are <a href="https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy">removed from the policy</a> before the policy is enforced if they are added using <code>&lt;meta&gt;</code> tag.
</p>
<h2 data-sanitized-id="invalid-security-headers-in-lessmetagreater">Invalid security headers in <code>&lt;meta&gt;</code></h2>
<p>One of the issues with adding security headers using <code>&lt;meta&gt;</code> tag is developers often assume that any of the HTTP security response headers can be added through this method. This is not true and can often lead to a false sense of security since any invalid security header will be ignored by the browser. It is important to remember that even though <code>http-equiv</code> helps in defining HTTP response headers, it is an <a href="https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute">enumerated attribute</a>. Hence, it can only help in delivering a fixed set of <a href="https://html.spec.whatwg.org/multipage/semantics.html#pragma-directives">HTTP header</a> and any other HTTP header that is set using this method will be ignored.</p>
<p><img src="https://htmhell.dev/images/advent2023/security-header-meta-usage.png" alt="Table showing security headers used in meta tags as per Web Almanac 2022 security chapter. 3.11% websites in desktop and 2.60% websites in mobile use meta tag to set Referrer-Policy. 0.55% websites in desktop and 0.47% websites in mobile use meta tag to set CSP. 0.08% websites in desktop and 0.06% websites in mobile use meta tag to set not allowed security headers."></p>
<p>In <a href="https://almanac.httparchive.org/en/2022/security#preventing-attacks-using-meta">Web Almanac 2022</a>, it was seen that 0.08% of the websites visited over the desktop had an invalid security header. So in conclusion, delivering Referrer-Policy and Content-Security-Policy security headers using the <code>&lt;meta&gt;</code> element can be really useful and protect your user's security and privacy, but it is also important to be aware of the limitations of this method.</p>
<h2 data-sanitized-id="further-reading">Further Reading:</h2>
<ul>
<li><a href="https://almanac.httparchive.org/en/2022/security#preventing-attacks-using-meta">Web Almanac 2022 Security: Preventing attacks using &lt;meta&gt;</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html">HTTP Security Response Headers Cheat Sheet</a></li>
<li><a href="https://html.spec.whatwg.org/multipage/semantics.html#pragma-directives">HTML spec: 4.2.5.3 Pragma directives</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html">Content Security Policy Cheat Sheet</a></li>
<li><a href="https://web.dev/articles/referrer-best-practices">Referer and Referrer-Policy best practices</a></li>
</ul>


guid: https://htmhell.dev/adventcalendar/2023/8/
title: The hidden depths of the input element
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/8/
hash: $+��22|��2c h
attributes: {"enclosures":[]}
content:
by Phil Nash<br><p>The <code>&lt;input&gt;</code> element is the most fascinating element in HTML.</p>
<p>Most elements behave the same way regardless of their attributes, the <code>type</code> attribute of the <code>&lt;input&gt;</code> element can take 1 of 22 different values that gives it not only different behaviour, but a different visual appearance too (many of which are hell to style, of course).</p>
<p>The <code>&lt;input&gt;</code> element is responsible for everything from text input, through checkboxes and radio buttons, to a button that resets all other elements in the form. I want to look beyond the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types">various types</a> that the <code>&lt;input&gt;</code> can embody, to the attributes you may not know about. Attributes that tweak the behaviour of the <code>&lt;input&gt;</code> to make it more usable, more accessible, and more applicable to various situations. Let's dig in.</p>
<h2 data-sanitized-id="keyboard-control">Keyboard control</h2>
<p>On a mobile device, focusing a text input will bring up the default virtual keyboard. But what if the input you are expecting is numerical or some other format? In this case the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#inputmode"><code>inputmode</code> attribute</a> can help to optimise the keyboard for the input you are expecting.</p>
<p>If the expected input is an email address, you can set the <code>inputmode</code> to “email” and the <code>@</code> symbol will surface on the main keyboard. If, however, you're looking for a number, then you can set the <code>inputmode</code> to “numeric” or “decimal” and the number keypad will appear. This is useful in cases where your input is a number, but the input <code>type</code> of <code>number</code> isn't appropriate, for example with a one time password or a credit card number.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>text<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">inputmode</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>numeric<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<p>The full list of <code>inputmode</code> values that you can use are listed below with their keyboard appearance on iOS. Android will display different variations.</p>
<ul>
<li><code>none</code>: stops virtual keyoards appearing for when you want to implement your own keyboard (you probably don't want this)</li>
<li><code>text</code>: the default</li>
<li><code>numeric</code>: just numbers</li>
<li><code>decimal</code>: numbers and a decimal point</li>
<li><code>tel</code>: numbers and other dial pad characters</li>
<li><code>url</code>: the normal keyboard, with a period, forward slash and ".com" in place of the space bar</li>
<li><code>email</code>: the normal keyboard, with a small space bar, an "@" symbol and a period</li>
<li><code>search</code>: the normal keyboard, with a slightly smaller space bar, a period and a highlighted button that says "go" rather than the normal "return"</li>
</ul>
<p><img src="https://htmhell.dev/images/advent2023/inputmode.webp" alt="An animation showing a browser on iOS tabbing through the different inputmodes, appearing as listed above."></p>
<p><code>inputmode</code> is also supported on <code>&lt;textarea&gt;</code>s and any element in <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#contenteditable"><code>contenteditable</code> mode</a>.</p>
<p>Get the right keyboard and your users will thank you as they input their data.</p>
<h2 data-sanitized-id="inactive-inputs">Inactive inputs</h2>
<p>If you need to display an <code>&lt;input&gt;</code> element, but you don't want a user to be able to change the contents or you intend to change the contents in some other programmatic way, you might think that enabling the <code>disabled</code> attribute will help. However, disabled input elements are removed from the accessibility tree and thus disappear for users of assistive technologies. The contents of disabled inputs are also not submitted with the form.</p>
<p>An alternative to <code>disabled</code> is the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly"><code>readonly</code> attribute</a>. It makes the field inert, but the content remains accessible and will be submitted with the rest of the inputs within the form.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>label</span> <span data-sanitized-class="token attr-name">for</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>The label<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>label</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>text<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">value</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>You can<span data-sanitized-class="token punctuation">'</span>t change this<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">readonly</span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<div data-sanitized-class="highlight u-mb">
<p><strong>Note by Manuel</strong>: Not all screen readers announce the field as “read only”. You may want to test it first and decide whether to support it with an additional description.</p>
</div>
<p>You can also target <code>readonly</code> elements in CSS with the pseudoclass <code>:read-only</code>. Elements that can be read and written can be targeted with the <code>:read-write</code> pseudoclass.</p>
<p><code>readonly</code> only works on <code>&lt;input&gt;</code>s that behave like text controls and is also supported on <code>&lt;textarea&gt;</code> elements.</p>
<p>Choosing whether to use <code>readonly</code> over <code>disabled</code> on an element is about choosing whether an element is usable on the page. Don't forget to consider this.</p>
<h2 data-sanitized-id="lights-camera-action">Lights, camera, action</h2>
<p>You might think the only way to have a user access the camera on their mobile device and take pictures or video is via JavaScript and the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia"><code>getUserMedia</code> API</a>. But camera access is also available declaratively. On a file upload <code>&lt;input&gt;</code> you can specify the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture"><code>capture</code> attribute</a>, which will direct the element to trigger the device camera.</p>
<p>Sadly this is a feature that is <a href="https://caniuse.com/html-media-capture">limited to the mobile versions of supporting browsers</a>, on a desktop it will work like a regular file upload field.</p>
<p>For devices with front and back cameras, you can even provide hints for which camera to use. So if you are asking your user to take a selfie, you can set <code>capture="user"</code> to prefer the front-facing camera. If you are more interested in pictures of the surroundings, you can set <code>capture="environment"</code> to hint that you'd prefer the rear facing camera. The device can override these hints if, for example, it doesn't have a user facing camera available.</p>
<p>You can also use the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept"><code>accept</code> attribute</a> to hint at the type of content you want from the camera. If you accept image types, then the camera will be primed to take pictures, but if you hint that you want video then the camera will set up to record instead.</p>
<p>So, using the following HTML will produce the result below.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>label</span> <span data-sanitized-class="token attr-name">for</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Cheeeeese<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>label</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>file<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">capture</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>user<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">accept</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>image/jpeg,image/png<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<p><img src="https://htmhell.dev/images/advent2023/capture.webp" alt='An animation showing a file input on a page using the capture attribute set to "user" and the accept attribute set to image types. When touching on the input the camera is opened, facing the user with only photo as an option. An image is captured and it appears as a small thumbnail in the file input.'></p>
<p>This is a great way to capture an avatar for a user with no need for JavaScript, just HTML.</p>
<h2 data-sanitized-id="spellbound">Spellbound</h2>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck"><code>spellcheck</code> attribute</a> is a generic attribute that can be used on any element. It's used to trigger the browser's spellchecking capability on elements where you also set <code>contenteditable="true"</code>. It also works on <code>&lt;input&gt;</code> elements and is definitely something you should consider.</p>
<p>Different browsers treat <code>&lt;input&gt;</code> elements differently. Firefox and Safari on the desktop will not spellcheck <code>&lt;input&gt;</code> elements without the attribute, but Chrome and Safari on iOS will. To get consistent behaviour across browsers, you should set the <code>spellcheck</code> attribute to "true" or "false" depending on the contents of the <code>&lt;input&gt;</code>.</p>
<p>For regular text, help your users out with <code>spellcheck="true"</code>. For identifiers, like usernames, email addresses, URLs, or other things that you would not expect to find in a dictionary, set <code>spellcheck="false"</code>.</p>
<p>One other thing to consider, how spellchecking is implemented is left to the browser. <a href="https://www.otto-js.com/news/article/chrome-and-edge-enhanced-spellcheck-features-expose-pii-even-your-passwords">Chrome and Edge have been known to send the contents of <code>&lt;input&gt;</code> elements to a spellchecking service over the network</a>, exposing whatever was in the field. If your <code>&lt;input&gt;</code> elements are capturing sensitive data, like personally identifiable information, like names or birth dates, or passwords, then you should set <code>spellcheck="false"</code> to avoid sharing it.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>label</span> <span data-sanitized-class="token attr-name">for</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Name<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>label</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>text<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">spellcheck</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>false<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<p>Note that a <code>&lt;input&gt;</code> with <code>type</code> of "password" won't leak its value, but if you implement a show password feature that sets the type to "text" then this opens the field to be spellchecked.</p>
<p>Getting your spellcheck settings consistent helps your users as they input data into your forms and being aware of the risks of spellcheck helps you protect your users' data.</p>
<h2 data-sanitized-id="automatic-shift">Automatic Shift</h2>
<p>The default behaviour for an <code>&lt;input&gt;</code> on a mobile device is to capitalise the first word and make all other characters lowercase. This is fine when writing a sentence, however things like names and addresses expect all words to be capitalised. Other inputs, like airline ticket identifiers and UK postcodes expect their input to be in all capitals.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize"><code>autocapitalize</code> attribute</a> is your friend in this case and saves your users having to use the shift key to produce text in the format you want it.</p>
<p>Capitalise the first letter of every word with <code>autocapitalize="words"</code> and capitalise every letter with <code>autocapitalize="characters"</code>.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>label</span> <span data-sanitized-class="token attr-name">for</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Name<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>label</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>text<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">name</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>full-name<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">autocapitalize</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>words<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<p>One thing to note, the <code>autocapitalize</code> attribute does not have an effect where it doesn't make sense, like on <code>&lt;input&gt;</code> elements with <code>type</code> of "email", "url", and, most importantly, "password".</p>
<p>Getting autocapitalisation right will make entering data on mobile devices much easier for your users.</p>
<h2 data-sanitized-id="less-typing-is-always-better">Less typing is always better</h2>
<p>Last but not least, my absolute favourite <code>&lt;input&gt;</code> attribute is the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete"><code>autocomplete</code> attribute</a>.</p>
<p>It provides hints for the browser to fill in input fields with data it already knows. The <code>autocomplete</code> attribute can take <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values">an enormous number of values</a> that associate an <code>&lt;input&gt;</code> with many things the browser knows or can find out.</p>
<p>For things like registration or logging in, you can set <code>autocomplete</code> to values like "username" or "email". For passwords, you can use the value "new-password" to prevent a browser from autocompleting an existing password and to suggest a secure new password. Or you can use the value "current-password" to indicate that the browser or password manager should autocomplete the field with the user's password.</p>
<p>You can autocomplete a user's address with values like "street-address", "country-name" and "postal-code". For ecommerce applications you can combine the values with additional identifiers to separate between "billing" and "shipping" addresses.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>label</span> <span data-sanitized-class="token attr-name">for</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Address<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>label</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>text<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">autocomplete</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>street-address shipping<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">name</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>street-address<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>input<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<p>For one time passwords sent over SMS, you can use the value "one-time-password" to indicate that the browser should look at recently received messages and extract the OTP for autocomplete.</p>
<p>And if you're implementing passkeys, you can use the <code>autocomplete</code> value "webauthn" to help implement <a href="https://web.dev/articles/passkey-form-autofill">passkey autofill</a>.</p>
<p>Correctly using the <code>autocomplete</code> attribute is even a <a href="https://www.w3.org/WAI/WCAG22/Techniques/html/H98">sufficient technique</a> to fulfill <a href="https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose">the WCAG level AA rule "identify input purpose"</a>. Providing fine-grained hints to the browser for the type of content the input fields are expecting means that they can be filled in saving time and typing for all users.</p>
<p>Conversely, there are times you might want to disable autocomplete entirely. Using <code>autocomplete="off"</code> will stop the browser remembering what was entered in an input. This is useful for fields that expect different input each time, like a text CAPTCHA.</p>
<p>Getting the right autocomplete attributes helps all users fill in inputs with the least amount of effort.</p>
<h2 data-sanitized-id="picking-the-right-attributes">Picking the right attributes</h2>
<p>Designing and developing forms that work for all users is hard. The <code>&lt;input&gt;</code> element has many options that you can choose to use to make your forms more usable and accessible.</p>
<p>For controlling the available virtual keyboards use <code>inputmode</code>. To control whether an <code>&lt;input&gt;</code> can be updated, but also still read and submitted, choose <code>readonly</code> over <code>disabled</code>. You can trigger the camera on mobile devices using <code>capture</code>. Use the <code>spellcheck</code> attribtue to control whether spellchecking is activated on an input, and remember to set it to "false" on sensitive inputs. Save users time by using <code>autocapitalize</code> to control the capitalisation of text in an input. And finally, use <code>autocomplete</code> to help the browser fill in the contents of an input and save your users typing.</p>
<p>All of these attributes, when deployed correctly will help your users fill in your forms with the least amount of effort.</p>


guid: https://htmhell.dev/adventcalendar/2023/9/
title: What the slot?
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/9/
hash: 0}�ebJ���M7 �J�
attributes: {"enclosures":[]}
content:
by Egor Kloos (aka dutchcelt)<br><p>Web Components. The discussion seems to pop up more than it used to.</p>
<ul>
<li><a href="https://jakelazaroff.com/words/web-components-will-outlive-your-javascript-framework/">Web Components Will Outlive Your JavaScript Framework</a></li>
<li><a href="https://blog.jim-nielsen.com/2023/html-web-components-an-example/">HTML Web Components: An Example</a></li>
<li><a href="https://github.com/material-components/material-web/discussions/5004">Material web components 1.0 release</a></li>
<li><a href="https://miriam.codes/2023/11/15/components/">HTML Web Components are Just JavaScript?</a></li>
</ul>
<p>Web Components offer nifty features for creating components using JavaScript, CSS, and HTML. They have been available in all major web browsers since 2017. That’s long enough to start using them in production.</p>
<p>Web Components consist of three things that are intended to work together.</p>
<ol>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">Custom Elements</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">Shadow DOM</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots">Templates and Slots</a></li>
</ol>
<p>The great thing is that you can use each of these on their own. For HTMHell and fun, let’s take a closer look at Slots and the <code>&lt;slot&gt;</code> element.</p>
<h2 data-sanitized-id="what-are-slots?">What are slots?</h2>
<p>For slots to do their thing, we need a Shadow DOM. MDN on what the Shadow DOM is:</p>
<blockquote>A set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality. In this way, you can keep an element's features private, so they can be scripted and styled without the fear of collision with other parts of the document.</blockquote>
<p>Slots only exist in the Shadow DOM and are a way to get content from the document (i.e. a web page).<br>
We have, in effect, two types of DOM. A Light DOM (the document) and the Shadow DOM attached to a <a href="https://web.dev/articles/custom-elements-v1">Custom Element</a> or a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow">basic set of HTML elements</a>.</p>
<p>Yes, you can have a Shadow DOM associated with a <code>&lt;div&gt;</code>, a <code>&lt;section&gt;</code> element, or a <code>&lt;main&gt;</code>!<br>
Slots are rather magical and can prove very useful in bridging these two DOMs.<br>
The best way to explain what a slot is is to show you. This basic example uses slots and a <code>&lt;div&gt;</code> element.</p>
<p data-height="420" data-default-tab="js,result" data-slug-hash="QWYORLN" data-user="dutchcelt" data-sanitized-class="codepen">
  <span>See the Pen <a href="https://codepen.io/dutchcelt/pen/QWYORLN">
  Attach a Shadow DOM to a DIV</a> by Egor Kloos (<a href="https://codepen.io/dutchcelt">@dutchcelt</a>)
  on <a href="https://codepen.io/">CodePen</a>.</span>
</p>
<h3 data-sanitized-id="here-is-another-example">Here is another example</h3>
<p>Let’s say you wanted to use a <code>&lt;slot&gt;</code> with a Custom Element and a <code>&lt;template&gt;</code>. For brevity’s sake, I only show the two HTML parts, not the entire Custom Element setup.</p>
<h4>Custom element markup</h4>
<p>This is the HTML you would write on your web page.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>card-component</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>h2</span> <span data-sanitized-class="token attr-name">slot</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>heading<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>A heading passed to the named slot 'heading'.<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>h2</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>blockquote</span><span data-sanitized-class="token punctuation">&gt;</span></span>This will be passed on to the unnamed slot. Lorem ipsum dolor...<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>blockquote</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>card-component</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<h4>Template for your Shadow DOM</h4>
<p>The Template is ‘appended’ to the Shadow DOM inside the Custom Element, and the <code>&lt;slot&gt;</code> pulls the content in. The Custom Element now renders the template with the content from the page.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>template</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>a</span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>linkheader<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>slot</span> <span data-sanitized-class="token attr-name">name</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>heading<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>slot</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>a</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>content<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>slot</span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>slot</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>template</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>The key is that you can place your <code>&lt;slot&gt;</code> into a more elaborate markup provided by the template.</p>
<h3 data-sanitized-id="the-crispy-bits">The crispy bits</h3>
<p>Here is a short list of what makes slots great:</p>
<ul>
<li>Content inside the Shadow DOM is <a href="https://alice.pages.igalia.com/blog/how-shadow-dom-and-accessibility-are-in-conflict/">not always as accessible</a> as on a normal web page. Slots allow your content to exist in both. The content remains on the page, and the Shadow DOM can access and enhance it.</li>
<li>Slots can be placed in any order. Especially with named slots, as we can see in the Codepen above. In the document source, the ‘heading’ is the first element but displayed as the second element through the template.</li>
<li>It is easy for machines to index (i.e. search engines and crawlers like chatGPT) because the content is still on the web page. <em>Note: Most search engines now index the Shadow DOM’s content.</em></li>
<li>Content changes on the page are passed on to the Shadow DOM. Slots are live; Ummm, I mean reactive!</li>
<li>You don’t need to style the slotted elements as the web page styling already styles them. Styles are passed along with the content.</li>
</ul>
<p>This all sounds very tasty. But before we choke on an unclosed <code>&lt;article&gt;</code> let’s see what’s whiffy about slots.</p>
<h3 data-sanitized-id="the-soggy-bits">The soggy bits</h3>
<p>This part requires a bit more explanation of the behaviour of Shadow DOM. Namely, it’s Encapsulation.<br>
CSS-Tricks has an introduction to Web Components and contains a useful part about <a href="https://css-tricks.com/an-introduction-to-web-components/#aa-shadow-dom">Shadow Dom</a></p>
<blockquote>… the shadow DOM works sort of like an <code>&lt;iframe&gt;</code> where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context.</blockquote>
<p>For me, this is the killer feature of Web Components. Nothing goes in or out—script contexts, IDs, styles, etc. Everything is nicely contained and controlled.<br>
But wait, isn’t one of the positives of slotted elements that they <s>smuggle</s> bring their given styles from the document <strong>with them</strong> to the Shadow DOM? Houston! We have a problem!</p>
<p>So maybe we don’t want those pesky styles from the document. But hey, at least we can just override them in the Shadow DOM. Right!?</p>
<p>Yes, we can! … Sometimes. — Note to reader: Feel free to insert random expletives —</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted">Slotted elements</a> have weak specificity by design. Let me demonstrate with yet another Codepen.</p>
<p data-height="420" data-default-tab="html,result" data-slug-hash="VwgQYjb" data-user="dutchcelt" data-sanitized-class="codepen">
  <span>See the Pen <a href="https://codepen.io/dutchcelt/pen/VwgQYjb">
  Slotted elements are weak</a> by Egor Kloos (<a href="https://codepen.io/dutchcelt">@dutchcelt</a>)
  on <a href="https://codepen.io/">CodePen</a>.</span>
</p>

<p>In this example, we can’t style the slotted element. This is because the document already applies a style to them. (See the CSS tab in Codepen)<br>
We can force it to work when we add an <code>!important</code> to the <code>::slotted</code> rule. Even if the document style also adds an <code>!important</code> the <code>::slotted</code> style still holds up.</p>
<pre data-sanitized-class="language-css"><code data-sanitized-class="language-css"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">/* Light DOM style */</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token selector">h2</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token property">color</span><span data-sanitized-class="token punctuation">:</span> green <span data-sanitized-class="token important">!important</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<pre data-sanitized-class="language-css"><code data-sanitized-class="language-css"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">/* Shadow DOM slotted styling */</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token selector">::slotted(h2)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token property">color</span><span data-sanitized-class="token punctuation">:</span> navy <span data-sanitized-class="token important">!important</span><span data-sanitized-class="token punctuation">;</span> <span data-sanitized-class="token comment">/* The colour is navy */</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>Well, that’s great, I mean GROSS!<br>
It's not as bad as you might think as this is a use-case where you do want to reverse the cascade using <code>!important</code>.</p>
<p>However, this means that styling slotted content from within the Shadow DOM always requires <code>!important</code> for each property. Remember that the initial style could matter so be very careful, especially if you don't have control over the content in the document.<br>
For most situations, you should accept that slotted elements keep the styling they're given. Only override when you really need to.</p>
<h3 data-sanitized-id="but-what-if-i-want-to-have-my-cake-and-eat-it?">But what if I want to have my cake and eat it?</h3>
<p>We effectively only have 1 way to override the style of slotted content for the Shadow DOM in a clean and inclusive way. Select both the shadowed element and the slotted elements from within the document and style it from there.</p>
<pre data-sanitized-class="language-css"><code data-sanitized-class="language-css"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token selector">.card-demo h2</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token property">color</span><span data-sanitized-class="token punctuation">:</span> navy<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>I confess adding these additional styles to the document is a bit weird. However, bundling these with the main styles is fine in most situations. This also allows document authors to explicitly apply an override when they need to.<br>
Before you do this, you should ask yourself again if you need these additional overrides. You may find, with some consideration, that you can make do without the added complexity.</p>
<h4>An alternative</h4>
<p>I have a work-a-round specific to my use case with Design System component libraries using Web Components. In this scenario, I have no control nor insight into the web page the library components are loaded. So I sometimes add some extra CSS to strengthen the slotted elements.</p>
<p>Because the component is explicitly being loaded anyway, I can directly insert <a href="https://web.dev/articles/constructable-stylesheets">Constructable Stylesheets</a> to the document—no need to require a separate instruction to the developer to load additional styling. The Web Component can do it for you.</p>
<p>This Codepen demonstrates how you can <a href="https://codepen.io/dutchcelt/pen/JjxpdKp">add overrides</a> to the document using <code>adoptedStyleSheets</code>.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Overriding the document styling</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">const</span> sheet <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token keyword">new</span> <span data-sanitized-class="token class-name">CSSStyleSheet</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br>sheet<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">replaceSync</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string"><br><span data-sanitized-class="highlight-line">  #demodiv {</span><br><span data-sanitized-class="highlight-line">    &amp; h2 {</span><br><span data-sanitized-class="highlight-line">      color: navy;</span><br><span data-sanitized-class="highlight-line">    }</span><br><span data-sanitized-class="highlight-line">    &amp; p {</span><br><span data-sanitized-class="highlight-line">      color: purple;</span><br><span data-sanitized-class="highlight-line">    }</span><br><span data-sanitized-class="highlight-line">    &amp; [slot="subheading"] {</span><br><span data-sanitized-class="highlight-line">      color: steelBlue;</span><br><span data-sanitized-class="highlight-line">      font-size: 1.3333rem;</span><br><span data-sanitized-class="highlight-line">      font-weight: 300;</span><br><span data-sanitized-class="highlight-line">    }</span><br><span data-sanitized-class="highlight-line">  }</span><br></span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span><br><span data-sanitized-class="highlight-line">document<span data-sanitized-class="token punctuation">.</span>adoptedStyleSheets<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">push</span><span data-sanitized-class="token punctuation">(</span>sheet<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>This is rather extreme, and I wouldn't necessarily recommend it. However, it is an interesting approach using new techniques like Constructable Stylesheets.</p>
<p>Slots, I love them, and I hate them. I also wouldn't want to go without them.</p>
<p>Regardless if you’ll use Shadow DOM in the future, I hope slots are now a little less of a mystery.</p>
<p><strong>Update</strong> (December 10th, 2023):<br>
Initially the described behaviour of <code>!important</code> was incorrect and has been corrected.</p>


guid: https://htmhell.dev/adventcalendar/2023/10/
title: Template for accessibility guidelines
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/10/
hash: ] u֧��/O�}6��
attributes: {"enclosures":[]}
content:
by Steve Frenzel<br><h2 data-sanitized-id="foreword">Foreword</h2>
<p>This template is opinionated and intended as a starting point for those who want to define how accessibility is dealt with in their company. It does not matter whether your title is developer, designer, project manager or something else.</p>
<p>I created it based on my experience working with a very small start-up, a digital agency with more than 250 employees and a monolithic company with very fixed structures.</p>
<p>When creating this template, I had the <a href="https://webaim.org/projects/million/#wcag">six most common WCAG failures</a> in mind, but also what processes take place when you collaborate with other people. Nothing is set in stone and it should be adapted in consultation with your colleagues, because accessibility is a group effort! More on that in <a href="https://htmhell.dev/adventcalendar/2023/10/#1-prependix">Prependix</a>.</p>
<p>As a reference, I refer to the <a href="https://www.w3.org/TR/WCAG22/">Web Content Accessibility Guidelines (WCAG) 2.2</a>, as they are popular and well documented. However, you are free to include a different one or more in this template, depending on your location and the requirements that need to be met.</p>
<p>With all that being said, let's have a look at the template! 👀</p>
<h2 data-sanitized-id="table-of-contents">Table of contents</h2>
<ol>
<li><a href="https://htmhell.dev/adventcalendar/2023/10/#1-prependix">Prependix</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/10/#2-design">Design</a><br>
2.1 <a href="https://htmhell.dev/adventcalendar/2023/10/#21-color-contrast">Color contrast</a><br>
2.2 <a href="https://htmhell.dev/adventcalendar/2023/10/#22-font-size">Font size</a><br>
2.3 <a href="https://htmhell.dev/adventcalendar/2023/10/#23-touch-target-size">Touch target size</a><br>
2.4 <a href="https://htmhell.dev/adventcalendar/2023/10/#24-conveying-meaning">Conveying meaning</a><br>
2.5 <a href="https://htmhell.dev/adventcalendar/2023/10/#25-focus-behaviour">Focus behaviour</a><br>
2.6 <a href="https://htmhell.dev/adventcalendar/2023/10/#26-media-controls">Media controls</a><br>
2.7 <a href="https://htmhell.dev/adventcalendar/2023/10/#27-user-flow">User flow</a><br>
2.8 <a href="https://htmhell.dev/adventcalendar/2023/10/#28-landmarks--annotations">Landmarks &amp; annotations</a><br>
2.9 <a href="https://htmhell.dev/adventcalendar/2023/10/#29-handover">Handover</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/10/#3-development">Development</a><br>
3.1 <a href="https://htmhell.dev/adventcalendar/2023/10/#31-semantic-html">Semantic HTML</a><br>
3.2 <a href="https://htmhell.dev/adventcalendar/2023/10/#32-skip-link">Skip link</a><br>
3.3 <a href="https://htmhell.dev/adventcalendar/2023/10/#33-heading-levels">Heading levels</a><br>
3.4 <a href="https://htmhell.dev/adventcalendar/2023/10/#34-typography">Typography</a><br>
3.5 <a href="https://htmhell.dev/adventcalendar/2023/10/#35-buttons--anchors">Buttons &amp; anchors</a><br>
3.6 <a href="https://htmhell.dev/adventcalendar/2023/10/#36-alternative-text">Alternative text</a><br>
3.7 <a href="https://htmhell.dev/adventcalendar/2023/10/#37-operability">Operability</a><br>
3.8 <a href="https://htmhell.dev/adventcalendar/2023/10/#38-preference-detection">Preference detection</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/10/#4-testing">Testing</a><br>
4.1 <a href="https://htmhell.dev/adventcalendar/2023/10/#41-browsers">Browsers</a><br>
4.2 <a href="https://htmhell.dev/adventcalendar/2023/10/#42-browser-extensions">Browser extensions</a><br>
4.3 <a href="https://htmhell.dev/adventcalendar/2023/10/#43-screen-reader">Screen reader</a><br>
4.4 <a href="https://htmhell.dev/adventcalendar/2023/10/#44-pipeline-integration">Pipeline integration</a><br>
4.5 <a href="https://htmhell.dev/adventcalendar/2023/10/#45-other-apps--tools">Other apps &amp; tools</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/10/#5-appendix">Appendix</a></li>
</ol>
<h2 data-sanitized-id="1-prependix">1. Prependix</h2>
<p>To ensure that our products are usable and accessible, we comply with <strong>level AA success criteria</strong> of the <a href="https://www.w3.org/TR/WCAG22/">Web Content Accessibility Guidelines (WCAG) 2.2</a>. For a better understanding of these guidelines, check out the following resources:</p>
<ul>
<li><a href="https://www.tempertemper.net/blog/wcag-but-in-language-i-can-understand">WCAG, but in language I can understand</a> by tempertemper</li>
<li><a href="https://intopia.digital/wp-content/uploads/2023/10/Intopia-WCAG-2.2-Map-Portrait-Mode.pdf">WCAG 2.2 Map</a> by Intopia</li>
<li><a href="https://not-checklist.intopia.digital/">Accessibility Not-Checklist</a> by Intopia</li>
</ul>
<p><strong>Accessibility is a group effort and not a one-person show.</strong> It goes beyond leaving it to designers or developers. Being aware and curious about it will not only help you, but also the people around you.</p>
<p><strong>It is not possible to create a product that is 100% accessible.</strong> However, this is no reason to give up or to not start at all. “Progress over perfection” is our approach, because every little improvement counts and will make someone’s life easier.</p>
<p><strong>You're never done with accessibility.</strong> Wether you're just starting your journey, or maintaining a current project: People (and their needs) are constantly changing, as well as technology, so you'll need to pay attention and adapt accordingly. If you want to get a better understanding of this particular topic, check out <a href="https://inclusive.microsoft.design/">Microsoft Inclusive Design</a>.</p>
<h2 data-sanitized-id="2-design">2. Design</h2>
<p>Whenever possible, we want to not only meet the minimum for level AA success criteria, but also go beyond that. It is therefore possible that there is no direct WCAG recommendation for some of our requirements.</p>
<h3 data-sanitized-id="21-color-contrast">2.1 Color contrast</h3>
<a href="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-color-contrast-example.png">
  <img alt="Illustration showcasing the difference of color contrast for an item on the Kaufland website. There are two images, both showing the contrast ratio. The first one has insufficient color contrast, showing values of 3.31:1 and 3.01:1. The second one is were requirements are met, with ratios of 4.6:1 and 4.65:1." src="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-color-contrast-example.png">
</a>
<p><strong>Color contrast</strong> must be level AA, if possible level AAA compliant. If text is in front of an image, provide a layer in between.</p>
<p><em>Related WCAG recommendation: <a href="https://www.w3.org/TR/WCAG22/#contrast-minimum">Success Criterion 1.4.3 Contrast (Minimum)</a></em></p>
<h3 data-sanitized-id="22-font-size">2.2 Font size</h3>
<a href="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-font-size-example.png">
  <img alt="Illustration showcasing the difference of font sizes on the german Samsung website. Both images showing the font sizes. The first one's font size is too small, with only 12 pixel. The second one is meeting the requirement with 14 pixel." src="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-font-size-example.png">
</a>
<p><strong>Font size</strong> is at least 14 pixels, preferably 16 pixels.</p>
<p><em>No related WCAG recommendation, this is based on real life experience.</em></p>
<h3 data-sanitized-id="23-touch-target-size">2.3 Touch target size</h3>
<a href="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-touch-target-size-example.png">
  <img alt="Illustration showcasing the difference of touch target sizes on the german Bosch website. Both images show a section of the footer, with three social media icons having a 2 pixel solid red broder around the, to visualise the target size. The first one's target size is too small with only 24 by 24 pixel (yet meeting the WCAG requirement). The second one's sufficient with 44 by 44 pixel." src="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-touch-target-size-example.png">
</a>
<p><strong>Touch target size</strong> should be at least 44 x 44 pixels, preferably more.</p>
<p><em>Related WCAG recommendation: <a href="https://www.w3.org/TR/WCAG22/#target-size-enhanced">Success Criterion 2.5.5 Target Size (Enhanced)</a></em></p>
<h3 data-sanitized-id="24-conveying-meaning">2.4 Conveying meaning</h3>
<a href="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-conveying-meaning-example.png">
  <img alt="Illustration showcasing the difference of meaningfull labels on the german Mango website. Both images show a section of the main navigation. The first one's only showing four icons but without a label. The second one's having labels associated to the icons: Search, log in, wishlist and cart." src="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-conveying-meaning-example.png">
</a>
<p><strong>Convey meaning</strong> not only through colors or icons. If possible, add a descriptive label to the element in question.</p>
<p><em>Related WCAG recommendation: <a href="https://www.w3.org/TR/WCAG22/#use-of-color">Success Criterion 1.4.1 Use of Color</a></em></p>
<h3 data-sanitized-id="25-focus-behaviour">2.5 Focus behaviour</h3>
<a href="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-focus-behaviour-example.png">
  <img alt="Illustration showcasing focus behaviour on the Rituals website. All the images show a section with a headline, some text and a 'Shop Now' button. The first image shows the button with no focus. The second image shows the focussed button, but it's dark blue focus is barely visible, as it's directly attached to the black button. The third image shows a black focus ring with 2 pixel distance on every side, making it much more visible." src="https://htmhell.dev/images/advent2023/accessibility-template/htmlhell-focus-behaviour-example.png">
</a>
<p>All interactive elements have a <strong>clearly visible focus state</strong>. It might be a change of background and font color, or a focus ring. If the latter, it should have a width and offset of at least 2 pixels.</p>
<p><em>Related WCAG recommendations:</em></p>
<ul>
<li><a href="https://www.w3.org/TR/WCAG22/#focus-visible">Success Criterion 2.4.7 Focus Visible</a></li>
<li><a href="https://www.w3.org/TR/WCAG21/#non-text-contrast">Success Criterion 1.4.11 Non-text Contrast</a></li>
<li><a href="https://www.w3.org/TR/WCAG22/#focus-not-obscured-minimum">Success Criterion 2.4.11 Focus Not Obscured (Minimum)</a></li>
</ul>
<h3 data-sanitized-id="26-media-controls">2.6 Media controls</h3>
<p>When creating components such as carousels (sometimes called sliders) or any sort of media player, make sure it has controls to:</p>
<ul>
<li>Navigate to the next or previous item</li>
<li>Stop and start the media</li>
<li>Show or hide captions (if applicable)</li>
</ul>
<p><em>Related WCAG recommendation:</em></p>
<ul>
<li><a href="https://www.w3.org/TR/WCAG22/#captions-prerecorded">Success Criterion 1.2.2 Captions (Prerecorded)</a></li>
<li><a href="https://www.w3.org/TR/WCAG22/#pause-stop-hide">Success Criterion 2.2.2 Pause, Stop, Hide</a></li>
</ul>
<h3 data-sanitized-id="27-user-flow">2.7 User flow</h3>
<p>When visualising how users interact with a certain feature or component, keep in mind they will not only use a mouse. They might also use a keyboard, screen readers or other assistive technologies. So ask yourself: Can this (thing I'm working on) be used by anyone at any time? Is there something that might exclude certain people?</p>
<p><em>Related WCAG recommendation: <a href="https://www.w3.org/TR/WCAG22/#operable">Operable</a></em></p>
<h3 data-sanitized-id="28-landmarks-and-annotations">2.8 Landmarks &amp; annotations</h3>
<p>Provide a version of your design with <strong>landmarks and accessibility annotations</strong>. This will help communicating with developers and improve accessibility across all browsers. You can find plenty of resources regarding this topic (and much more) in Stephanie Walter's <a href="https://stephaniewalter.design/blog/a-designers-guide-to-documenting-accessibility-user-interactions/">Designer’s Guide to Documenting Accessibility &amp; User Interactions</a>.</p>
<h3 data-sanitized-id="29-handover">2.9 Handover</h3>
<p>A design is only ready for development, if it has been <strong>approved by at least one other member of your team</strong> (Note by the author: This could be a developer, another designer, quality assurance, accessibility expert, or whoever else might be responsible). Before moving on, all open issues must be resolved.</p>
<p>(Note by the author: All things mentioned are supposed to help you avoid the most common accessibility mistakes but this list isn't extensive and there's much more to consider. Again, this template is supposed be a starting point. Feel free to extend it however you want!)</p>
<h2 data-sanitized-id="3-development">3. Development</h2>
<p>Putting the following rules into practice will ensure a robust, easy-to-use UI which will not only improve accessibility, but in certain cases might also be benfecial for SEO.</p>
<h3 data-sanitized-id="31-semantic-html">3.1 Semantic HTML</h3>
<p>Use <strong>semantic HTML</strong> wherever possible to leverage native browser behavior. If progressive enhancement is needed, apply it with CSS and / or JavaScript.</p>
<p><em>Related MDN documentation:</em> <a href="https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement">Progressive enhancement</a></p>
<h3 data-sanitized-id="32-skip-link">3.2 Skip link</h3>
<p>Add a <strong>skip link</strong> so keyboard users can jump straight to the main content. This also requires a <code>&lt;main&gt;</code> element present in the DOM. Related links:</p>
<ul>
<li><a href="https://webaim.org/techniques/skipnav/#creating">Creating "Skip Navigation" Links</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main">Documentation <code>&lt;main&gt;</code> element</a></li>
</ul>
<h3 data-sanitized-id="33-heading-levels">3.3 Heading levels</h3>
<p><strong>Heading levels</strong> should be used appropriately and not be skipped. Also do not use heading elements simply for the sake of styling! If a heading needs to look a certain way, apply a CSS class instead.</p>
<p><em>Related MDN documentation:</em> <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements">The HTML Section Heading elements</a><br>
<em>Related WCAG recommendation: <a href="https://www.w3.org/TR/WCAG22/#section-headings">Success Criterion 2.4.10 Section Headings</a></em></p>
<h3 data-sanitized-id="34-typography">3.4 Typography</h3>
<p>For <strong>typography</strong>, use a relative unit like <code>rem</code> instead of <code>px</code>. This will give users the ability to adjust the font size to their needs. Also install fonts locally instead of fetching them from an external source to improve performance.</p>
<p><em>Related article:</em> <a href="https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/#accessibility-considerations-5">The Surprising Truth About Pixels and Accessibility</a></p>
<h3 data-sanitized-id="35-buttons-and-anchors">3.5 Buttons &amp; anchors</h3>
<p>Make sure you use the right element when deciding between <strong>a button and a link</strong> (actually called <strong>anchor</strong>). A button does something, an anchor element changes the URL in the browser. There are exceptions though, for example an anchor with the <code>email</code> or <code>tel</code> attribute. Both elements should have an accessible name for optimal screen reader support and search engine optimisation.</p>
<p><em>Related MDN documentation:</em></p>
<ul>
<li><a href="https://developer.mozilla.org/de/docs/Web/HTML/Element/button">Documentation <code>button</code> element</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a">Documentation <code>a</code> element</a></li>
</ul>
<p><em>Related article:</em></p>
<ul>
<li><a href="https://css-tricks.com/buttons-vs-links/">Button versus links</a></li>
</ul>
<h3 data-sanitized-id="36-alternative-text">3.6 Alternative text</h3>
<p>Images must have <strong>descriptive alternative text</strong>. If they are purely decorative, use <code>alt=""</code>. Otherwise provide <a href="https://design102.blog.gov.uk/2022/01/14/whats-the-alternative-how-to-write-good-alt-text/">a sufficient image description</a>. If you must provide an icon-only interaction, make sure it has an accessible name by using <code>aria-label</code>.</p>
<p><em>Related WCAG recommendation: <a href="https://www.w3.org/TR/WCAG22/#non-text-content">Success Criterion 1.1.1 Non-text Content</a></em></p>
<h3 data-sanitized-id="37-operability">3.7 Operability</h3>
<p>Interactive elements can be <strong>operated by mouse, keyboard, screen reader and other assistive technology</strong>. If such an element must consist only of an icon or color (which it should not), provide an accessible name with <code>aria-label</code>.</p>
<h3 data-sanitized-id="38-preference-detection">3.8 Preference detection</h3>
<p>Some people have a <strong>preference for how content should be displayed</strong> on their devices. For example, they may have a dark theme activated on their operating system, or a high contrast mode. It is also possible that they prefer motion or transparency to be reduced. To respect all these scenarios, the following media queries can be applied:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-color-scheme</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast">prefers-contrast</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion">prefers-reduced-motion</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-transparency">prefers-reduced-transparency</a></li>
</ul>
<h2 data-sanitized-id="4-testing">4. Testing</h2>
<p>Whether you work in design, development or quality assurance: Testing is an essential part of the job and happens during the process and before you ship anything.</p>
<h3 data-sanitized-id="41-browsers">4.1 Browsers</h3>
<p><strong>Use different browsers to test</strong> what you are working on. Google <strong>Chrome</strong> (Chromium engine) and Mozilla <strong>Firefox</strong> (Gecko engine) can be used on any operating system, except Apple iOS. When using MacOS, use Apple <strong>Safari</strong> (WebKit engine) as well.</p>
<h3 data-sanitized-id="42-browser-extensions">4.2 Browser extensions</h3>
<p><strong>Browser extensions</strong> are a free and quick way to run accessibility scans. Most of them are based on the axe-core library and a mix of the following will cover enough ground. However, there are many more extensions out there worth exploring and testing.</p>
<ul>
<li><a href="https://www.deque.com/axe/devtools/">axe DevTools</a></li>
<li><a href="https://www.getstark.co/">Stark</a> (To generate reports)</li>
<li><a href="https://wave.webaim.org/">WAVE</a></li>
</ul>
<h3 data-sanitized-id="43-screen-reader">4.3 Screen reader</h3>
<p>A <strong>screen reader</strong> will help you to understand the structure of a page or element. It will also show you a different way of user interaction. On <strong>Windows</strong> you can use <a href="https://www.freedomscientific.com/products/software/jaws/">JAWS</a> (paid) or <a href="https://www.nvaccess.org/download/">NVDA</a> (free). <a href="https://www.apple.com/accessibility/vision/">VoiceOver</a> is available on all <em>Apple</em> devices for free. For <strong>Android</strong> you can use <a href="https://support.google.com/accessibility/android/answer/6283677?hl=en">Talkback</a>. Related link: <a href="https://www.sarasoueidan.com/blog/testing-environment-setup/#guides-to-browsing-and-navigating-content-with-a-screen-reader">Guides to browsing and navigating content with a screen reader by Sara Soueidan</a></p>
<h3 data-sanitized-id="44-pipeline-integration">4.4 Pipeline integration</h3>
<p>Integrating accessibility checks into the <strong>deployment pipeline</strong> is another great way to ensure requirements are met. For example a push or merge will not be possible if there are linting errors or a failed unit test. Related links:</p>
<ul>
<li><a href="https://www.adrianbolonio.com/blog/accessibility-github-actions">Automating the accessibility tests of your source code with GitHub Actions</a></li>
<li><a href="https://www.getstark.co/blog/introducing-stark-for-developers-beta">Stark for GitHub</a></li>
</ul>
<h3 data-sanitized-id="45-other-apps-and-tools">4.5 Other apps &amp; tools</h3>
<p>To check <strong>WCAG conformance</strong> of color contrast, use the app <a href="https://www.tpgi.com/color-contrast-checker/">Colour Contrast Analyser by TPGi</a>. You can also check the color contrast in the browser using <a href="https://webaim.org/resources/contrastchecker/">WebAim Contrast Checker</a>.</p>
<p>When using <strong>Figma</strong>, there are a couple of great plugins to check for accessibility. <strong>Stark</strong> has a great UI and the free version offers <strong>contrast check, adding landmarks and vision simulator</strong>. Another noteworthy plugin is <a href="https://www.figma.com/community/plugin/731310036968334777/A11y---Focus-Order">A11y - Focus Order</a>.</p>
<h2 data-sanitized-id="5-appendix">5. Appendix</h2>
<p>This is a dynamic document and may be subject to change over time. If content or links are no longer up-to-date, please contact <em>[name of colleague]</em> via <em>[communication tool of your choice]</em> or reach out via e-mail at <em>[fake@email.com]</em>.</p>


guid: https://odysee.com/science-unshackled-family-review-with:c7d31712106efb9cd2d5b2c2be17d13dcca4eac5
title: Science Unshackled Family Review with Matt Ehret and the Holmes Clan
author: ;Rising Tide Foundation
link: https://odysee.com/science-unshackled-family-review-with:c7d31712106efb9cd2d5b2c2be17d13dcca4eac5
hash: N�G#N��$(1�$X}
content:
<p><img src="https://thumbnails.lbry.com/URBQgcj3clQ" width="480" alt="thumbnail" title="Science Unshackled Family Review with Matt Ehret and the Holmes Clan"></p>Matt Ehret and the Holmes Family discuss the content of the new book 'Science Unshackled: Restoring Causality to a World in Chaos'.<br><br>The previous RTF interview with the Holmes family can be viewed here:<br>https://youtu.be/ngJg9nKxjD0<br><br>Purchase Science Unshackled here:<br>https://matthewehret.substack.com/p/my-new-book-has-now-hit-the-press<br><br>and other books published by Matt and Cynthia here:<br>https://canadianpatriot.org/untold-history-of-canada-books/<br><br>www.risingtidefoundation.net<br>...<br>https://www.youtube.com/watch?v=URBQgcj3clQ

attributes:
{"enclosures":[{"url":"https://player.odycdn.com/api/v3/streams/free/science-unshackled-family-review-with/c7d31712106efb9cd2d5b2c2be17d13dcca4eac5/6c6001.mp4","type":"video/mp4","length":418749090}]}


guid: https://htmhell.dev/adventcalendar/2023/11/
title: The hidden attribute in HTML
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/11/
hash: y�K�:�?+:U���
attributes: {"enclosures":[]}
content:
by Ahmad El-Alfy<br><p>The <code>hidden</code> attribute allows us to hide HTML elements from the page. When it was introduced, it worked in a very simple way: it set the CSS <code>display</code> property to <code>none</code>.</p>
<p><a href="https://twitter.com/getifyX/status/1131118316901326848">Many people voiced concerns</a> because here we are, mixing styles with markup again. To be fair, this was at a time before the rise of frameworks and the change in people's mindsets where separation of concern became a less debatable issue. <a href="https://meowni.ca/hidden.is.a.lie.html">There were also others</a> who opposed the attribute because it can be easily overridden by CSS. A solution to avoid hidden elements from showing unintentionally is adding this snippet to your stylesheet to ensure it overrides other rules used on the same element.</p>
<pre data-sanitized-class="language-css"><code data-sanitized-class="language-css"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token selector">[hidden]</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token property">display</span><span data-sanitized-class="token punctuation">:</span> none <span data-sanitized-class="token important">!important</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>There are differing opinions on using the <code>!important</code> declaration, as some authors considered it code smell. Later, the use of <code>!important</code> in utility classes became more accepted.</p>
<div data-sanitized-class="highlight u-mb"><p>A modern way to mitigate the use of <code>!important</code> is the use of CSS Cascade Layers. CSS layers is implemented by using the <code>@layer</code> directive, allowing us to define specific layers for our styles. This allow us to control the specificity and ordering of the CSS rules without resorting to <code>!important</code>. This approach promotes a more maintainable and scalable codebase, reducing the reliance on <code>!important</code> declarations and making it easier to manage styles across different components and elements. All we need to do is to add the previous snippet in the utility layer and moving that layer to be the last. This way we can ensure that their styles take precedence without the need for <code>!important</code>.</p></div>
<p>Let's take an example where we need to toggle the display of an element. We can do that by setting its <code>display</code> property to <code>none</code> using JavaScript and inline CSS. This is a common approach that many follow to hide an element instantly. To show the element again we can change the display back to whatever value we want.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Hide the element</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span>style<span data-sanitized-class="token punctuation">.</span>display <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token string">'none'</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Show the element again by setting the display to another value</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span>style<span data-sanitized-class="token punctuation">.</span>display <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token string">'block'</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>The problem with setting the display to something other than <code>none</code> is that we could be dealing with an element that has different display values according to the media size for example (e.g. <code>block</code>, <code>flex</code> ... etc). In that case, setting it to a specific value can introduce some troubles if the display types don't match.</p>
<p>Another approach would be to use the <code>removeProperty</code> method, available on the elements <code>style</code> property, which removes specific inline styles.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Show the element again by removing the inline declared property</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span>style<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">removeProperty</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'display'</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>One argument in favor of the <code>hidden</code> attribute is its semantic value. It clearly indicates that an element using the attribute is intended to be hidden from display. Another argument is that we can hide content in HTML without needing CSS. So without having to set inline styles, we can add and remove the attribute on the element as we want.</p>
<pre data-sanitized-class="language-javascript"><code data-sanitized-class="language-javascript"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Hide the element</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span>hidden <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token boolean">true</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// We can also use addAttribute</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">setAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'hidden'</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token string">''</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// Show the element</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span>hidden <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token boolean">false</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// We cal also use removeAttribute</span></span><br><span data-sanitized-class="highlight-line">el<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">removeAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">'hidden'</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<h2 data-sanitized-id="the-future-of-hidden-attribute">The future of <code>hidden</code> attribute</h2>
<p>Initially, the <code>hidden</code> attribute was considered a boolean attribute. If the attribute was present on an HTML element, it would be effective regardless of its value:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">hidden</span><span data-sanitized-class="token punctuation">&gt;</span></span>This element will be hidden<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">hidden</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>hidden<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>This element is also hidden<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">hidden</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>false<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>This one is hidden as well<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>The <a href="https://github.com/whatwg/html/pull/7475">specification has changed recently</a>, and the <code>hidden</code> attribute is now an enumerated attribute. This means it can have specific named values. The new value introduced in the specification is <code>until-found</code>, which triggers a new state called the "Until Found" state.</p>
<p>If the <code>hidden</code> attribute is present without a value, or with any value other than <code>until-found</code>, it will behave as it did before. However, if the value is <code>until-found</code>, the element will be in the "Until Found" state. In this state, the element will be hidden until it is found by the user agent.</p>
<h2 data-sanitized-id="the-hidden-until-found-state">The hidden until found state</h2>
<p>The hidden until found state was created to solve a common problem. Let's look at tabs and accordions for example. These UI components contain elements that are undiscoverable until a certain action is done (e.g clicking a button). This makes the content unavailable to screen readers, search engines, and the built-in find-in-page feature in browsers.</p>
<p>Unlike the standard "hidden" state, which completely hides the content by setting its <code>display</code> property to <code>none</code>, the "hidden until found" state uses the <code>content-visibility</code> property to hide content while still making it discoverable by search engines, find-in-page functionality, and linking. Consider the following example (currently only supported in Chrome and Edge).</p>
<iframe title="Hidden Attribute" scrolling="no" loading="lazy" width="100%" height="500" src="https://v17.livecodes.io/?x=id/rie7i3ccg28&amp;embed=true" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" sandbox="allow-scripts allow-same-origin">
  See the project <a href="https://v17.livecodes.io/?x=id/rie7i3ccg28" target="_blank">Hidden Attribute</a> on <a href="https://livecodes.io/" target="_blank">LiveCodes</a>.
</iframe>
<p>In this example, try using the browser's search feature (find-in-page) and type the word "banana." After the first result is highlighted, attempting to navigate to the next result will reveal the "Introduction" section. Following that, the subsequent sections will become visible in sequence. Note that after an element is revealed, the attribute is removed keeping the element visible. Clicking the "Reset Display" button will hide the sections again, enabling you to retry the process. Additionally, clicking any of the links will automatically display the corresponding sections.</p>
<p>Note that this example doesn't rely on JavaScript, except for the functionality used to reset the display. Instead, the behavior is achieved purely with HTML. As the corresponding sections are "found," the hidden attribute is automatically removed from the elements, triggering their visibility.</p>
<h2 data-sanitized-id="browser-support">Browser support</h2>
<p>Presently, this functionality is accessible in Chrome and Edge. While Firefox has expressed an <a href="https://github.com/mozilla/standards-positions/issues/612">intention</a> to incorporate it, there are <a href="https://lists.webkit.org/pipermail/webkit-dev/2022-March/032142.html">no indications</a> of similar developments from Safari yet. Currently, the feature is marked as "Experimental," indicating that its functionality may undergo changes in the future.</p>
<h2 data-sanitized-id="wrapping-up">Wrapping up</h2>
<p>The anticipated enhancements to the hidden attribute are poised to positively influence and enhance the user experience for those utilizing the find-in-page feature in browsers. The behavior showcased in <a href="https://twitter.com/JosephArhar/status/1567279577160126464">this demo</a> by Joey Arhar aligns with our expectations when searching for keywords that match content on a page, reinforcing the potential benefits of these improvements.</p>
<h2 data-sanitized-id="resources">Resources</h2>
<ul>
<li><a href="https://github.com/whatwg/html/pull/7475">The pull request that adds <code>hidden="until-found"</code> attribute</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden">Hidden attribute on MDN</a></li>
<li><a href="https://twitter.com/JosephArhar/status/1567279577160126464">Joey Arhar demo showing hidden until found on Wikipedia</a></li>
</ul>
<h2 data-sanitized-id="special-thanks">Special thanks</h2>
<p>Many thanks to Saptak Sengupta and Eric Bailey for their invaluable reviews of this post; their insightful feedback greatly enhanced the final result. A special acknowledgment goes to Manuel Matuzovic for accepting my contribution to this year's Advent calendar and his patient support until I completed this blog. Thanks for offering a platform where we can share our thoughts and discussions about HTML.</p>


guid: https://htmhell.dev/adventcalendar/2023/12/
title: Test-driven HTML and accessibility
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/12/
hash: �T�|JOW5~����
attributes: {"enclosures":[]}
content:
by David Luhr<br><p>When I started writing unit tests and following a <a href="https://en.wikipedia.org/wiki/Test-driven_development">test-driven development (TDD)</a> workflow, I was stoked with the immediate feedback and confidence I gained in every line of JavaScript I wrote.</p>
<p>TDD improved my software design with simpler, more predictable code. It saved me countless hours of manual debugging by reporting errors directly in failing tests. And with solid test coverage, it allowed me to add new logic and functionality while keeping everything in an always-working state.</p>
<p>I followed the process of <a href="https://www.jamesshore.com/v2/blog/2005/red-green-refactor">Red, Green, Refactor</a>, meaning I wrote a failing test, wrote just enough code to make the test pass, and then freely cleaned up my code, knowing everything was working. My JavaScript code had never been better.</p>
<p>Then one day, I wrote a UI-oriented test that used <code>document.querySelector()</code>. Before the test even finished running, it errored and revealed that JavaScript tests run outside of the browser in Node, where no document object model (DOM) exists. Since the focus of my career is evaluating, designing, and developing accessible user interfaces, primarily with HTML and CSS, I wanted to apply TDD techniques to HTML and accessibility. But it seemed like my dream testing setup wasn't possible.</p>
<p>Months passed of continuing to enjoy TDD with JavaScript, while wishing I could do more to test my accessible markup. Most of my research pointed to slow, flaky approaches like scripting an entire <a href="https://en.wikipedia.org/wiki/Headless_browser">headless browser</a> just to check if an HTML element rendered or if a menu opened on click.</p>
<p>Other testing libraries use synthetic (faked) interactions by dispatching DOM events that might lead to false positives in tests if they don't accurately replicate real user interaction. Plus, these libraries don't have the necessary DOM events for all interactions. I wanted to write small, focused, fast unit tests that provided trustworthy, immediate feedback with every change I made.</p>
<p>Then I came across a tool called <a href="https://modern-web.dev/docs/test-runner/overview/">Web test runner</a> from the team behind <a href="https://open-wc.org/">Open Web Components</a>. Instead of running JavaScript tests in Node, Web Test Runner executes tests directly in the browser.</p>
<p>This is a big deal for multiple reasons:</p>
<ol>
<li>Tests run your production JavaScript code in the production environment—the browser</li>
<li>Tests have access to the real DOM, unlocking the ability to write tests for HTML, accessibility, and even CSS</li>
<li>Tests can send native interactions, such as actual mouse clicks and keyboard input, instead of faking them with DOM events</li>
<li>And it's even possible do things like set the viewport dimensions to test responsive design</li>
</ol>
<h2 data-sanitized-id="shift-accessibility-feedback-earlier">Shift accessibility feedback earlier</h2>
<p>With this approach, I write tests to capture my expectations around semantic markup and accessibility upfront, then get instant feedback on whether my code meets those expectations.</p>
<p>Let's say I expect paragraph (<code>&lt;p&gt;</code>) elements shouldn't be empty. I can write the following test, which gets all <code>&lt;p&gt;</code> elements, loops over them, and expects each one doesn't have empty text content:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"paragraphs"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token keyword">const</span> docAllParagraphs <span data-sanitized-class="token operator">=</span> Array<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">from</span><span data-sanitized-class="token punctuation">(</span>document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelectorAll</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"p"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"have text content"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">		docAllParagraphs<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">paragraph</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">			<span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>paragraph<span data-sanitized-class="token punctuation">.</span>textContent<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">""</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">		<span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>With access to the real DOM in tests, I can continuously check if I'm using heading levels, ARIA, and landmark regions correctly. I can build custom UI patterns and ensure they respond to keyboard input and manage focus as expected. And as I add new features or refactor my code, my tests re-run with every change to make sure I don't harm accessibility as the project evolves.</p>
<p>Accessibility-focused linters give feedback as you code, but have limited awareness outside of very specific violations. Automated accessibility audits and most manual review happens after you write the code. Test-driven accessibility is about providing continuous feedback before, during, and after you write the code.</p>
<p>Using all these forms of accessibility testing together provides the most value. Unit testing HTML allows me find, fix, and prevent accessibility issues as I'm working. It also allows me to focus my manual accessibility reviews on more holistic evaluations, such as navigating through a page with a screen reader to asses the user experience.</p>
<p>This test-driven approach makes something possible that I've been striving for in my work: an accessibility-first workflow. With an accessibility-first mindset, we plan for and preserve accessibility from the beginning of a project instead of leaving it as an afterthought. If I could achieve one thing in my career, it would be to make accessibility-first a foundational practice alongside usability, responsive design, performance, and security.</p>
<h2 data-sanitized-id="evolve-code-with-confidence">Evolve code with confidence</h2>
<p>In addition to getting feedback upfront and continuously thereafter, having tests allows us to change our code freely as features and user needs evolve.</p>
<p>The downside of automated accessibility testing tools and manual review is they can't be performed continuously, and often focus on the current page in the browser. We either have to manually re-test with each change we make exhaustively across all pages, or we depend on getting feedback after batches of changes.</p>
<p>With tests in place, we can keep our code (and accessibility) in an always-working state. Every time we make a change, our tests run across the entire codebase. If our tests pass, we can more confidently and frequently deploy our changes to users.</p>
<h2 data-sanitized-id="the-setup">The setup</h2>
<p>To begin testing our HTML in the browser, we first install our dependencies:</p>
<pre data-sanitized-class="language-bash"><code data-sanitized-class="language-bash"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">npm</span> i --save-dev @web/test-runner @web/test-runner-mocha @esm-bundle/chai @web/test-runner-commands @web/test-runner-playwright</span></code></pre>
<p>We need <a href="https://modern-web.dev/docs/test-runner/overview/">Web test runner</a> to run tests in the browser, Web Test Runner's implementation of Mocha for directly testing HTML files, Chai for test assertions, Web Test Runner commands for sending native interactions (click, keyboard, etc.) to our UI elements, and Web Test Runner's implementation of Playwright for headless browsers to run the tests in.</p>
<p>With this setup, we run our tests in Chromium, Firefox, and WebKit all at once to make sure our code works across the major browsers. This happens super quickly in the background and works in different operating systems.</p>
<p>Even through browser cross-compatibility continues to improve, this testing setup reveals discrepancies that would be incredibly difficult to otherwise detect. For example, I recently found that getting the value of an element's <code>role</code> attribute with <code>Element.role</code> was failing only in Firefox, and using <code>Element.getAttribute("role")</code> was the fix. I'm not sure if or how I would have found that issue without tests that run directly in each of the browsers.</p>
<p>Next, we add a couple scripts to our <code>package.json</code> to run tests either in a single pass or in watch mode:</p>
<pre data-sanitized-class="language-json"><code data-sanitized-class="language-json"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token property">"scripts"</span><span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token property">"test"</span><span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token string">"web-test-runner \"/**/*.test.js\" --node-resolve --playwright --browsers chromium firefox webkit"</span><span data-sanitized-class="token punctuation">,</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token property">"test:watch"</span><span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token string">"web-test-runner \"/**/*.test.js\" --node-resolve --playwright --browsers chromium firefox webkit --watch"</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">,</span></span></code></pre>
<p>Last, we can run our watch mode script as we work. This will run all tests, then detect any file changes and re-run only the impacted tests:</p>
<pre data-sanitized-class="language-bash"><code data-sanitized-class="language-bash"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">npm</span> run test:watch</span></code></pre>
<p>If our website is made with simple HTML pages, we can run the tests directly in the HTML files (with a <code>&lt;script&gt;</code> tag) to test specific markup. If our HTML is rendered with JavaScript, as is the case with web components and many other approaches, then we can test the rendering in JS files alongside any logic or functionality that we'd usually unit test. We'll compare the tradeoffs of these two options at the end of this article.</p>
<h2 data-sanitized-id="writing-tests">Writing tests</h2>
<p>To demonstrate the value of this setup, let's write tests to prevent some of the most common (non-visual) WCAG 2 failures found in the annual <a href="https://webaim.org/projects/million/#wcag">WebAIM Million study</a>:</p>
<ul>
<li>Low contrast text</li>
<li>Missing alt text</li>
<li>Empty links</li>
<li>Missing form input labels</li>
<li>Empty buttons</li>
<li>Missing document language</li>
</ul>
<p>Writing tests for low contrast text is possible but too involved for this post. So let's focus on tests for the remaining most common failures.</p>
<p>First, we'll create a test file called <code>example.test.js</code> with the required imports:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">import</span> <span data-sanitized-class="token punctuation">{</span> runTests <span data-sanitized-class="token punctuation">}</span> <span data-sanitized-class="token keyword">from</span> <span data-sanitized-class="token string">"@web/test-runner-mocha"</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">import</span> <span data-sanitized-class="token punctuation">{</span> expect <span data-sanitized-class="token punctuation">}</span> <span data-sanitized-class="token keyword">from</span> <span data-sanitized-class="token string">"@esm-bundle/chai"</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">import</span> <span data-sanitized-class="token punctuation">{</span> sendKeys <span data-sanitized-class="token punctuation">}</span> <span data-sanitized-class="token keyword">from</span> <span data-sanitized-class="token string">"@web/test-runner-commands"</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// TODO: Write tests here</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p><code>runTests</code> allows us to directly test HTML files, which makes this setup quick and easy for static HTML. <code>@web/test-runner-commands</code> provides native interaction utilities, such as <code>sendKeys</code>, that we'll use in a later example.</p>
<p>Then we'll create an example HTML file called <code>example.html</code> with the usual boilerplate and link to the test file:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token doctype"><span data-sanitized-class="token punctuation">&lt;!</span><span data-sanitized-class="token doctype-tag">DOCTYPE</span> <span data-sanitized-class="token name">html</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>html</span> <span data-sanitized-class="token attr-name">lang</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>en<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>head</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>meta</span> <span data-sanitized-class="token attr-name">charset</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>UTF-8<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>meta</span> <span data-sanitized-class="token attr-name">name</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>viewport<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">content</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>width=device-width, initial-scale=1.0<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>title</span><span data-sanitized-class="token punctuation">&gt;</span></span>Our test file<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>title</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>script</span> <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>module<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">src</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>example.test.js<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token script"></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>script</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>head</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>body</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token comment">&lt;!-- TODO: Write markup here --&gt;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>body</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>html</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<h3 data-sanitized-id="missing-alt-text">Missing alt text</h3>
<p>In <code>example.test.js</code>, let's write a test to check for missing image alt text:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"images"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docAllImages <span data-sanitized-class="token operator">=</span> Array<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">from</span><span data-sanitized-class="token punctuation">(</span>document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelectorAll</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"img"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"have an alt attribute"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllImages<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">image</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>image<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"alt"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">,</span> image<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>This test gets all images in the DOM, loops over them, and checks to make sure each one has an <code>alt</code> attribute.</p>
<p>Error messages in JavaScript include the line and character numbers to help quickly locate and fix errors, but we don't have access to this with HTML elements. By passing <code>image.outerHTML</code> as the second argument of our <code>expect()</code> function, we can include the element in the error message to make finding and correcting the offending element easier. We'll use this for custom error messages any time we're checking all instances of an element.</p>
<p>If this is too verbose or noisy for more complex elements, we could instead only include the element's opening tag, or assign all elements unique <code>data-test-id</code> values to identify them directly in test error messages.</p>
<p>In our <code>example.html</code> file, let's add an image without an <code>alt</code> attribute to get a failing test:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>img</span> <span data-sanitized-class="token attr-name">src</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>/example.jpg<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span></code></pre>
<p>Web Test Runner reports the following in the command line:</p>
<pre data-sanitized-class="language-bash"><code data-sanitized-class="language-bash"><span data-sanitized-class="highlight-line">❌ images <span data-sanitized-class="token operator">&gt;</span> have an alt attribute</span><br><span data-sanitized-class="highlight-line">	AssertionError: <span data-sanitized-class="token operator">&lt;</span>img <span data-sanitized-class="token assign-left variable">src</span><span data-sanitized-class="token operator">=</span><span data-sanitized-class="token string">"/example.jpg"</span><span data-sanitized-class="token operator">&gt;</span>: expected null to exist</span><br><span data-sanitized-class="highlight-line">		at example.test.js:9:41</span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">Chromium: <span data-sanitized-class="token number">1</span> failed</span><br><span data-sanitized-class="highlight-line">Firefox: <span data-sanitized-class="token number">1</span> failed</span><br><span data-sanitized-class="highlight-line">Webkit: <span data-sanitized-class="token number">1</span> failed</span></code></pre>
<p>Our test fails as expected. To get the test to pass, we simply need to add an <code>alt</code> attribute, either as an empty string <code>""</code> for decorative content or with a useful description for meaningful content.</p>
<p>The neat thing about this test is it passes if no <code>&lt;img&gt;</code> elements exist, but fails if they exist and don't have an <code>alt</code> attribute. So, this test can be used from the beginning of any project without creating irrelevant noise.</p>
<p>Let's write tests for other common WCAG 2 failures.</p>
<h3 data-sanitized-id="empty-links">Empty links</h3>
<p>We'll add another test in <code>example.test.js</code>:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"links"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docAllLinks <span data-sanitized-class="token operator">=</span> Array<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">from</span><span data-sanitized-class="token punctuation">(</span>document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelectorAll</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"a"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"have a non-empty href attribute"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllLinks<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">link</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">const</span> hrefValue <span data-sanitized-class="token operator">=</span> link<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"href"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>hrefValue<span data-sanitized-class="token punctuation">,</span> link<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>hrefValue<span data-sanitized-class="token punctuation">,</span> link<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">""</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"are not empty"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllLinks<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">link</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>link<span data-sanitized-class="token punctuation">.</span>textContent<span data-sanitized-class="token punctuation">,</span> link<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">""</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>Similar to the previous test, we get all links from the DOM, loop over them, and check that their text content is not empty. We're also checking that links have a non-empty <code>href</code> attribute, just as an example of other HTML validation we'd like to do.</p>
<h3 data-sanitized-id="missing-form-input-labels">Missing form input labels</h3>
<p>Time for another test suite:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"form inputs"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docAllFormInputs <span data-sanitized-class="token operator">=</span> Array<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">from</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">      document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelectorAll</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"input, textarea, select"</span><span data-sanitized-class="token punctuation">)</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"have a dedicated label"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllFormInputs<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">formInput</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">const</span> inputId <span data-sanitized-class="token operator">=</span> formInput<span data-sanitized-class="token punctuation">.</span>id<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>inputId<span data-sanitized-class="token punctuation">,</span> formInput<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">const</span> inputLabel <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">label[for="</span><span data-sanitized-class="token interpolation"><span data-sanitized-class="token interpolation-punctuation punctuation">${</span>inputId<span data-sanitized-class="token interpolation-punctuation punctuation">}</span></span><span data-sanitized-class="token string">"]</span><span data-sanitized-class="token template-punctuation string">`</span></span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>inputLabel<span data-sanitized-class="token punctuation">,</span> formInput<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>inputLabel<span data-sanitized-class="token punctuation">.</span>textContent<span data-sanitized-class="token punctuation">,</span> formInput<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">""</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>In this test, we get all form input elements, loop over them, and assert multiple things:</p>
<ol>
<li>Each form input element needs an <code>id</code> attribute</li>
<li>We expect to find a <code>&lt;label&gt;</code> element with a <code>for</code> attribute that has the corresponding input's <code>id</code> as the value</li>
<li>We expect the <code>&lt;label&gt;</code> element to not be empty</li>
</ol>
<p>It's possible to provide an accessible name for form inputs with other techniques, such as <code>aria-label</code> or <code>aria-labelledby</code>, but I'm being opinionated here because I prefer actual <code>&lt;label&gt;</code> elements and text content. Testing for an accessible name through other means is just as easy.</p>
<h3 data-sanitized-id="empty-buttons">Empty buttons</h3>
<p>This is nearly identical to our empty links test:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"buttons"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docAllButtons <span data-sanitized-class="token operator">=</span> Array<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">from</span><span data-sanitized-class="token punctuation">(</span>document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelectorAll</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"button"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"are not empty"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllButtons<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">button</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>button<span data-sanitized-class="token punctuation">.</span>textContent<span data-sanitized-class="token punctuation">,</span> button<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">""</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<h3 data-sanitized-id="missing-document-language">Missing document language</h3>
<p>Let's write a test for the final common WCAG 2 failure:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"document"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"has a set language"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token keyword">const</span> languageValue <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"html"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"lang"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>languageValue<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"en"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>This one is the most straightforward, but valuable nonetheless. If your site has internationalization, you'd want to check the <code>lang</code> value matches the current locale and updates based on user preference.</p>
<h2 data-sanitized-id="testing-heading-levels">Testing heading levels</h2>
<p>We're starting to build a universal test suite that will preserve accessibility across our projects.</p>
<p>Let's add some further assertions around heading levels, which are a common source of errors in accessibility audits:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"headings"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docAllHeadings <span data-sanitized-class="token operator">=</span> Array<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">from</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">      document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelectorAll</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"h1, h2, h3, h4, h5, h6"</span><span data-sanitized-class="token punctuation">)</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"have &lt;h1&gt; element as the first heading"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">getHeadingLevel</span><span data-sanitized-class="token punctuation">(</span>docAllHeadings<span data-sanitized-class="token punctuation">[</span><span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">]</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">,</span></span><br><span data-sanitized-class="highlight-line">        docAllHeadings<span data-sanitized-class="token punctuation">[</span><span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">]</span><span data-sanitized-class="token punctuation">.</span>outerHTML</span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token number">1</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"have a single &lt;h1&gt;"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllHeadings<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">heading<span data-sanitized-class="token punctuation">,</span> index</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token comment">// Don't fail the test if the first heading on the page is `&lt;h1&gt;`</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">if</span> <span data-sanitized-class="token punctuation">(</span>index <span data-sanitized-class="token operator">===</span> <span data-sanitized-class="token number">0</span> <span data-sanitized-class="token operator">&amp;&amp;</span> <span data-sanitized-class="token function">getHeadingLevel</span><span data-sanitized-class="token punctuation">(</span>heading<span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">===</span> <span data-sanitized-class="token number">1</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">          <span data-sanitized-class="token keyword">return</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token function">getHeadingLevel</span><span data-sanitized-class="token punctuation">(</span>heading<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">,</span> heading<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token number">1</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"don't skip heading levels"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docAllHeadings<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">forEach</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">heading<span data-sanitized-class="token punctuation">,</span> index</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">let</span> previousHeadingLevel <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">const</span> currentHeadingLevel <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token function">getHeadingLevel</span><span data-sanitized-class="token punctuation">(</span>heading<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token keyword">if</span> <span data-sanitized-class="token punctuation">(</span>index <span data-sanitized-class="token operator">!==</span> <span data-sanitized-class="token number">0</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">          previousHeadingLevel <span data-sanitized-class="token operator">=</span> <span data-sanitized-class="token function">getHeadingLevel</span><span data-sanitized-class="token punctuation">(</span>docAllHeadings<span data-sanitized-class="token punctuation">[</span>index <span data-sanitized-class="token operator">-</span> <span data-sanitized-class="token number">1</span><span data-sanitized-class="token punctuation">]</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token punctuation">}</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>currentHeadingLevel<span data-sanitized-class="token punctuation">,</span> heading<span data-sanitized-class="token punctuation">.</span>outerHTML<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>be<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">lessThanOrEqual</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">          previousHeadingLevel <span data-sanitized-class="token operator">+</span> <span data-sanitized-class="token number">1</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token keyword">function</span> <span data-sanitized-class="token function">getHeadingLevel</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token parameter">heading</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token keyword">return</span> <span data-sanitized-class="token operator">+</span>heading<span data-sanitized-class="token punctuation">.</span>tagName<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">toLowerCase</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">replace</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"h"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token string">""</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span></span></code></pre>
<p>With these tests, we can ensure heading levels are used properly in our page. We should only have a single <code>&lt;h1&gt;</code> heading. This heading should also be the first heading on the page. Lastly, headings should never skip levels, such as <code>&lt;h2&gt;</code> followed by <code>&lt;h4&gt;</code>. We make use of small utility function, <code>getHeadingLevel()</code>, to keep our code more concise and mistake-proof.</p>
<p>Although these tests enforce correct heading logic, they can't evaluate if heading levels are appropriate for the content they describe. That always requires thoughtful consideration. Tests free the developer to spend more time on wider UX considerations like this.</p>
<h2 data-sanitized-id="testing-user-interaction">Testing user interaction</h2>
<p>So far, we've made simple assertions about our static HTML to make sure it's valid and not causing common accessibility failures. But as we create interactive patterns in our UI, the accessibility considerations become much more complex. We need to use HTML and ARIA to create custom semantics. We need to handle click, tap, and keyboard events. And we need to display and hide content, all while updating multiple attributes in concert.</p>
<p>Let's follow the Red, Green, Refactor workflow as we build a custom disclosure (show/hide) pattern.</p>
<p>We'll start with our tests in <code>example.test.js</code> to make sure our initial HTML and ARIA are correct:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"disclosure"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docDisclosureToggle <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">[data-component="disclosureToggle"]</span><span data-sanitized-class="token template-punctuation string">`</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token keyword">const</span> docDisclosureContent <span data-sanitized-class="token operator">=</span> document<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">querySelector</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token template-string"><span data-sanitized-class="token template-punctuation string">`</span><span data-sanitized-class="token string">[data-component="disclosureContent"]</span><span data-sanitized-class="token template-punctuation string">`</span></span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"has a toggle button"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">.</span>tagName<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"BUTTON"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"has a toggle button with the expected ARIA attributes"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"aria-expanded"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"aria-expanded"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token string">"false"</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"aria-controls"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"aria-expanded"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token string">""</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"has a content panel with the corresponding controls ID"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureContent<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureContent<span data-sanitized-class="token punctuation">.</span>id<span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">        docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"aria-controls"</span><span data-sanitized-class="token punctuation">)</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"has a hidden content panel by default"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureContent<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"hidden"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>These tests will fail, meaning we're in the "Red" stage. Now, we can author our initial HTML in <code>example.html</code> to get our tests to pass:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>button</span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token attr-name">type</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>button<span data-sanitized-class="token punctuation">"</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token attr-name">aria-expanded</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>false<span data-sanitized-class="token punctuation">"</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token attr-name">aria-controls</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>disclosureContent<span data-sanitized-class="token punctuation">"</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token attr-name">data-component</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>disclosureToggle<span data-sanitized-class="token punctuation">"</span></span></span><br><span data-sanitized-class="token punctuation">&gt;</span></span><br><span data-sanitized-class="highlight-line">  Open disclosure</span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>disclosureContent<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">data-component</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>disclosureContent<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">hidden</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>p</span><span data-sanitized-class="token punctuation">&gt;</span></span>Disclosure content<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>p</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>Our tests now pass, so we're in the "Green" stage. I'd make a Git commit at this point. If there are any improvements we'd like to make, we can make them with confidence as long as our tests are passing, and commit again each time we're in a working state.</p>
<p>Let's write a failing test to begin interacting with our disclosure in <code>example.test.js</code>:</p>
<pre data-sanitized-class="language-js"><code data-sanitized-class="language-js"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token function">runTests</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token function">describe</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"disclosure"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token comment">// ...</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token function">it</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"opens the disclosure on keyboard Enter press"</span><span data-sanitized-class="token punctuation">,</span> <span data-sanitized-class="token keyword">async</span> <span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span> <span data-sanitized-class="token operator">=&gt;</span> <span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">      docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">focus</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token keyword">await</span> <span data-sanitized-class="token function">sendKeys</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token punctuation">{</span></span><br><span data-sanitized-class="highlight-line">        down<span data-sanitized-class="token operator">:</span> <span data-sanitized-class="token string">"Enter"</span><span data-sanitized-class="token punctuation">,</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureToggle<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"aria-expanded"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">equal</span><span data-sanitized-class="token punctuation">(</span></span><br><span data-sanitized-class="highlight-line">        <span data-sanitized-class="token string">"true"</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token function">expect</span><span data-sanitized-class="token punctuation">(</span>docDisclosureContent<span data-sanitized-class="token punctuation">.</span><span data-sanitized-class="token function">getAttribute</span><span data-sanitized-class="token punctuation">(</span><span data-sanitized-class="token string">"hidden"</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">.</span>to<span data-sanitized-class="token punctuation">.</span>not<span data-sanitized-class="token punctuation">.</span>exist<span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"></span><br><span data-sanitized-class="highlight-line">      <span data-sanitized-class="token comment">// TODO: Reset the disclosure</span></span><br><span data-sanitized-class="highlight-line">    <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line">  <span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token punctuation">}</span><span data-sanitized-class="token punctuation">)</span><span data-sanitized-class="token punctuation">;</span></span></code></pre>
<p>With this test, we move focus to our toggle button, then use the <code>sendKeys</code> function from Web Test Runner commands to send a native keyboard <code>Enter</code> key press. We're checking that our toggle button gets updated attributes, and that our disclosure content is no longer hidden. We'd want similar tests for mouse click and the keyboard <code>Space</code> press, as well as testing that the disclosure closes on these interactions if it's already open.</p>
<p>We're in the "Red" stage again, but I'll leave the rest of the work to you to add the remaining tests and get them to pass. A cool thing is that the functionality needed for this component to work also creates some handy utility functions for our tests, such as resetting the disclosure at the end of each test to create a consistent starting point for other tests. And these utilities are easy to unit test with this approach as well.</p>
<h2 data-sanitized-id="wrapping-up">Wrapping up</h2>
<p>This workflow for test-driven HTML and accessibility provides so much value in my daily work, and I hope teams can adopt this technique to create a more responsible and usable web for everyone. I've used this setup extensively in large projects with complex UI patterns and it has scaled gracefully with several hundred tests running at all times.</p>
<p>With this setup, we can write unit tests that have access to the real DOM across major browsers, which allows us to check our HTML for validity, interactivity, and accessibility. We can run these tests in multiple browsers at once with every change, and preserve accessibility from the very start of our project. These expectations are permanently captured in our code, so we can freely refactor our work and add new features without introducing regressions.</p>
<p>The examples in this post assume a static HTML file that we directly linked our tests to. This is the fastest and most direct way to test our HTML, but we don't want our test file to load in production. As a result, we'd need to remove the <code>&lt;script&gt;</code> tag before deploying to the web. To make this approach more convenient, it'd be good to do this automatically with a build command that creates a <code>/dist</code> folder or something similar.</p>
<p>Most projects probably won't just have static HTML files, and instead render components with JavaScript. In this case, we create standalone <code>.test.js</code> files that import corresponding JavaScript modules and call render functions or methods directly. From there, the workflow and benefits are all the same, but there may be a little more effort in setting up specific UI situations to test vs. having HTML ready to go.</p>
<p>As a final consideration, this testing technique <em>enhances</em>, not <em>replaces</em>, other forms of accessibility testing and shifts a lot of the feedback earlier in the process. Using accessibility-focused code linters, automated accessibility testing tools, and manual accessibility review together provides the most value.</p>
<p>Many of the examples we covered in this post are checks we would otherwise have to make manually, either through code review or running accessibility audit tools. By writing unit tests for accessibility, we can discover and fix more issues as they arise. And we free more capacity to manually evaluate other aspects of accessibility, such as using assistive technology to evaluate the flow of a page or automated testing tools to check color contrast.</p>
<p>Automated tools cover about <a href="https://www.deque.com/automated-accessibility-testing-coverage/">30% of the WCAG success criteria and detect about 57% of overall issues</a>, leaving the rest for manual evaluation. With this approach, unit tests can increase coverage, particularly with interactive UI patterns. Even with more automated test coverage, manual review remains the most effective accessibility testing technique for finding issues and building empathy.</p>
<p>My long-term goal with this work is to build a robust, universal accessibility test collection that can be used across projects. As we explored with our disclosure example, it's also possible to create test suites for custom UI patterns such as accordions, menus, tooltips, and others.</p>
<p>Let me know if you'd be interested in using this test collection or if you'd like to collaborate. Together, we can make test-driven accessibility and accessibility-first development a reality.</p>


guid: https://scottjehl.com/posts/even-responsiver-video/
title: Extending Responsive Video with HTML Web Components
author: ;Scott Jehl
link: https://scottjehl.com/posts/even-responsiver-video/
hash: ڧ���������
attributes: {"enclosures":[]}
content:
<p>Responsive HTML video is a web standard again, and with recent patches to Firefox (<a href="https://phabricator.services.mozilla.com/D189416"><em>oh hey!</em></a>) and Chrome that match Safari's preexisting support, it now works across all modern browsers! That means you can use media queries for delivering your videos and potentially save your users some precious bytes.</p>
<p>As I've noted in <a href="https://scottjehl.com/posts/using-responsive-video/">prior</a> <a href="https://scottjehl.com/posts/responsive-video/">articles</a>, this is great news for web performance fans, as video is by far the heaviest type of media used on websites (the median weight of just the video files on pages that use video <a href="https://httparchive.org/reports/page-weight#bytesVideo">is <strong>almost 5 megabytes per page</strong> on mobile devices</a>), and that weight has a huge impact on performance, users' data costs, sites' hosting costs, and overall energy usage. Using an HTML video element to display a static video file is one of the most common and portable ways to drop a video into a webpage today, so it's great we now have options to do it more responsibly.</p>
<p>If you haven't yet caught up on how responsive video works, I'd recommend reading <a href="https://scottjehl.com/posts/using-responsive-video/">How to Use Responsive Video (...and Audio!)</a> first as a primer, because it's loaded with examples of how to use it and also when to consider alternatives, like HTTP Live Streaming.</p>
<h2>What You'll Learn Today...</h2>
<p>In this post, I'm going to talk briefly about responsive video, but most of the post will be about using HTML web components to extend native video behavior in very helpful ways. But even if you're not particularly interested in  video development, stick around as I'll demonstrate how to build an HTML Web Component to progressively enhance anything you need.</p>
<h2>How Responsive Video Works (<em>...and doesn't</em>)</h2>
<p>To start, let's recap what responsive video <em>is</em>. I'll start with some example HTML and then explain what we're looking at...</p>
<pre><code data-sanitized-class="language-html">&lt;video&gt;
	&lt;source src="/sandbox/video-media/small.mp4" media="(max-width: 599px)"&gt;
	&lt;source src="/sandbox/video-media/large.mp4"&gt;
&lt;/video&gt;
</code></pre>
<p>That's a basic responsive video element. It's responsive because one of the <code>source</code> elements has a <code>media</code> attribute on it that includes a media query, making its source selection conditional to the browser's viewport size. If you've used the <code>picture</code> element before, that <code>media</code> attribute is going to look familiar, because <code>picture</code> <strong>was actually designed</strong> based on how this works in HTML <code>video</code> (<em>back when responsive video was supported the first time around, which is a long story</em>).</p>
<p>Here's that <code>video</code> element live in the page. The latest versions of common browsers will see a video that says "small video source" at small viewport sizes and a video that says "large video source" at wider viewport sizes. Unsupporting, older browsers will simply select the first source suitable they find (small.mp4 in this case) because those browsers ignore the media attribute and pick the first source that matches, ignoring any that come after it.</p>
<video controls="controls" loop="" preload="none">
	<source src="https://scottjehl.com/sandbox/video-media/small.mp4" media="(max-width: 599px)">
	<source src="https://scottjehl.com/sandbox/video-media/large.mp4">
</video>
<p>So that's the basics of how a responsive video element works.</p>
<h3>How It Doesn't Work</h3>
<p>One thing that folks (me included!) tend to find surprising about responsive video is that unlike other "responsive" features in web design–CSS Media Queries or the <code>picture</code> element for example–responsive video sources are only assessed once, when the video (and typically the entire page) first loads.</p>
<p>That means that while you'll still enjoy the performance benefits of an appropriately selected source at page load, it won't be reassessed <em>after</em> page load when media conditions change (when, for example, when the user resizes their browser window). It's entirely possible that may change one day, but there are a number of reasons this behavior was not included in the initial implementation, such as complications involved in syncing up the timecode after a source is swapped, as well as the reasonable goal of matching the behavior of the existing implementation in Safari.</p>
<p>Regardless of all that, folks are right to find this part of the feature a little... unexpected.</p>
<p>Which got me thinking, what would it take to make that behavior work on top of what we already have in browsers today?</p>
<p>Let's find out.</p>
<h2>Extending Video with an HTML Web Component</h2>
<p>Web Components are having a moment right now, and particularly, "HTML Web Components", which are being used to describe web components that add behavior to regular old HTML by wrapping it in a custom element and applying a little JavaScript to enhance that markup further.</p>
<p>An HTML Web Component is an ideal tool for extending the functionality of standard HTML, like say, a video element for example! I'm going to walk through how I built one that'll make responsive video <em>even responsiver</em>, so it reacts to media changes after page load, should they occur.</p>
<h3>First, a Custom Element</h3>
<p>To start, we will need a custom element, which is just an HTML element that you invent that has at least one dash in its name. I'll go with <code>responsive-video</code>, like this:</p>
<pre><code data-sanitized-class="language-html">&lt;responsive-video&gt;&lt;/responsive-video&gt;
</code></pre>
<p>Nice. So that's a custom element, and with a little added JavaScript it could become a web component. But in order to make it an "HTML Web Component," we'll want to wrap that component around some HTML that is already meaningful and functional on its own, such as our <code>video</code> element above.</p>
<p>Here's that <code>video</code> element wrapped in a <code>responsive-video</code> custom element.</p>
<pre><code data-sanitized-class="language-html">&lt;responsive-video&gt;
	&lt;video controls autoplay loop&gt;
		&lt;source src="/sandbox/video-media/small.mp4" media="(max-width: 599px)"&gt;
		&lt;source src="/sandbox/video-media/large.mp4"&gt;
	&lt;/video&gt;
&lt;/responsive-video&gt;
</code></pre>
<p>That’s about it for the HTML part of this article. Based on what I’ve done so far, browsers will just ignore that custom element, (<em>though you can style it already if you want to</em>).</p>
<p>To get browsers to recognize this as an element with its own unique properties and behavior, you'll need to write a web component. Let's do that.</p>
<h3>Adding the JavaScript</h3>
<p>To enhance this HTML, we’ll create and load a JavaScript module that will contain our web component code. That code will live in a new file called <code>responsivevideo.js</code>.</p>
<p>Please note the last line of the code below to see how we can import that script into our page:</p>
<pre><code data-sanitized-class="language-html">&lt;responsive-video&gt;
	&lt;video controls autoplay loop&gt;
		&lt;source src="/sandbox/video-media/small.mp4" media="(max-width: 599px)"&gt;
		&lt;source src="/sandbox/video-media/large.mp4"&gt;
	&lt;/video&gt;
&lt;/responsive-video&gt;
&lt;script type="module" src="responsivevideo.js"&gt;&lt;/script&gt;
</code></pre>
<p>Now let’s look at the code we’ll put inside that file.</p>
<h3>Writing the Web Component Logic</h3>
<p>Web Components are designed for Progressive Enhancement. They come with a series of lifecycle events that are incredibly helpful for applying behavior to elements at moments such as when they're created or when they're appended to the DOM, and also for removing behavior and event handlers when an element is removed from the DOM or destroyed altogether.</p>
<p>Before we get ahead of ourselves on that, let's start with some boilerplate code for our <code>responsive-video</code> component.</p>
<pre><code data-sanitized-class="language-js">class ResponsiveVideo extends HTMLElement {}
customElements.define("responsive-video", ResponsiveVideo);
</code></pre>
<p>That’s not a lot of code, but it is already doing a lot of work. To start, it defines a class called <code>ResponsiveVideo</code>, which extends the <code>HTMLElement</code> class, which is the same class that all standard HTML elements–you know, <code>&lt;p&gt;</code>, <code>&lt;select&gt;</code>, <code>&lt;input&gt;</code>, etc.–use for their own behavior in the browser. After that, we're using <code>customElements.define</code> to actually define a new HTML element (the tag part) called <code>&lt;responsive-video&gt;</code> that will inherit the class's logic and behavior.</p>
<p>So now we have a Web Component, but it’s still not doing anything. Inside our class, we can start to use those lifecycle events I had mentioned to add (and remove) behavior and event handlers to the HTML at appropriate moments. I recommend <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">reading up on all of the features of web components</a>, but for the purpose of this article I'll cover just a few lifecycle events that are relevant to adding the behavior we want:</p>
<ul>
<li>constructor</li>
<li>connectedCallback</li>
<li>disconnectedCallback</li>
</ul>
<h4>The Constructor</h4>
<p>The <code>constructor</code> of a web component is called when an instance of its HTML element is created. It's useful for establishing some upfront variables within the element that'll be useful to later events and internal logic. It's often not the best place for all of our initialization code though because when the constructor is called, the element may not be appended to the DOM quite yet, so the many conditions relating to the page around it won't yet be accessible.</p>
<p>Here's our code with a constructor call added:</p>
<pre><code data-sanitized-class="language-js">class ResponsiveVideo extends HTMLElement {
	constructor() {
		super();
		console.log(this);
	}
}
customElements.define("responsive-video", ResponsiveVideo);
</code></pre>
<p>Inside the constructor, I started by calling <code>super()</code>. Generally, it's advised that we include this step first because it allows the component to gain access to the base class's (that is, <code>HTMLElement</code>) properties. Just after the <code>super()</code>, I've logged <code>this</code> to the browser console, which refers to the HTML element we're enhancing.</p>
<p>If you were to check the browser console at this point, you would see that the HTML element itself was logged: "responsive-video"</p>
<p>For now, I won’t use the constructor for anything, but in my final component I am going to use it to define some upfront variables that other events and methods will use.</p>
<h2>Using connectedCallback &amp; disconnectedCallback</h2>
<p>Next, I'll want to apply behavior to any <code>responsive-video</code> element when it is added to the DOM, and remove behavior when/if it's ever removed from the DOM. It's worth pointing out here that in contrast to the days when we used to wait for "DOM Ready" to query the DOM for elements to make our enhancements, these events are a major upgrade!</p>
<p>I know I’m going to use these two events, so first I’ll add them with some console logs.</p>
<pre><code data-sanitized-class="language-js">class ResponsiveVideo extends HTMLElement {
	constructor() {
		super();
		// upfront variables will go here
	}
	connectedCallback(){
		console.log(`${this} is in the DOM`);
	}
	disconnectedCallback(){
		console.log(`${this} has been removed from the DOM`);
	}
}
customElements.define("responsive-video", ResponsiveVideo);
</code></pre>
<p>Above, we're logging to the browser console whenever a <code>responsive-video</code> element shows up in the DOM (which includes when the page first loads), and whenever one is removed from the DOM.</p>
<p><code>connectedCallback</code> is a good place to create useful references to elements inside my component. For example, many of my methods will reference the video element inside the component, so I can make a reference to it inside <code>connectedCallback</code> like so, using <code>this.querySelector</code> to search within the scope of the component itself instead of the entire document.</p>
<pre><code data-sanitized-class="language-js">connectedCallback() {
	this.video = this.querySelector("video");
}
</code></pre>
<p>With these handy methods, we now have the tools we need to add some logic.</p>
<h3>Adding Custom Behavior</h3>
<p>The rest of our component’s code will serve one purpose: instructing the <code>video</code> element to select and load a new, appropriate source for the video element whenever/if media conditions change in a way that's relevant to its <code>source</code> elements.</p>
<p>To do that, we’ll write some code that applies this general flow:</p>
<ul>
<li>Find all of the <code>source</code> elements inside the <code>video</code> that have <code>media</code> attributes, and pass those media attribute values (which will be media queries or types) to <code>matchMedia</code>, which we can use to start listening to the media queries for relevant changes. Once listening, <code>matchMedia</code>'s "change" event will fire whenever those media queries start or stop matching the media conditions of the browser, which can happen when a user resizes their browser to a different size, for example.</li>
<li>When any of the media queries inside the video fire a change event, check if the currently playing video source is coming from a <code>source</code> element prior to the <code>source</code> element whose media just changed. If it is, then we can ignore that particular media change because any sources after the source that's currently playing are irrelevant to selection. On the other hand, if the currently playing source of the video happens to be the source whose media did just change (presumably by no longer matching), then we should instruct the video element to load again (using <code>video.load()</code>) using its native selection to find the best source.</li>
</ul>
<p>That's mostly it!</p>
<p>Here's how that logic will be structured in the code in our component, with our <code>connectedCallback</code> and <code>disconnectedCallback</code> events applying code at opportune moments. In addition to those lifecycle events, I've also added 4 custom methods for my own code to use: <code>bindMediaListeners</code>, <code>unbindMediaListeners</code>, <code>previousSiblingIsPlaying</code>, and <code>reloadVideo</code>.</p>
<p>I've replaced some of the code in those methods below with comments that describe how it flows.</p>
<pre><code data-sanitized-class="language-js">class ResponsiveVideo extends HTMLElement {
	constructor() {
		super();
		// upfront variables will go here
	}
	connectedCallback() {
		this.video = this.querySelector('video');
		this.bindMediaListeners();
	}
	disconnectedCallback() {
		this.unbindMediaListeners();
	}
	bindMediaListeners(){
		// loop through the source elements inside this.video and set up listeners for their media queries
		// when any of those listeners fire a change event,
		// make sure the source element of the event is relevant using previousSiblingIsPlaying
		// if it's relevant, call reloadVideo to find a new source
	}
	unbindMediaListeners(){
		// unbind the media query listeners
	}
	previousSiblingIsPlaying(elem, src) {
		// a helper function that will return true or false for whether a previous source element is currently selected
	}
	reloadVideo(){
		// capture the currentTime of the video
		// reload the video, let the browser choose the best current source
		// set the current time of the video to where it last was
	}
}
customElements.define("responsive-video", ResponsiveVideo);
</code></pre>
<p>That was a big chunk of code, but it should give you an idea of the flow for how it works.</p>
<h2>Completing the Enhancements</h2>
<p>Next, let’s populate that scaffolding with the actual logic. Here’s our full web component code.</p>
<pre><code data-sanitized-class="language-js">class ResponsiveVideo extends HTMLElement {
	constructor() {
		super();
		this.listenedMedia = [];
		this.reloadQueued = false;
	}
	connectedCallback() {
		this.video = this.querySelector('video');
		this.bindMediaListeners();
	}
	disconnectedCallback() {
		this.unbindMediaListeners();
	}
	bindMediaListeners(){
		this.querySelectorAll('source').forEach(source =&gt; {
			if (source.media) {
				const mqListener = () =&gt; {
					if ((source.src === this.video.currentSrc || !this.previousSiblingIsPlaying(source, this.video.currentSrc)) &amp;&amp; !this.reloadQueued) {
						this.reloadVideo();
					}
				};
				this.listenedMedia.push({ media: source.media, handler: mqListener });
				window.matchMedia(source.media).addEventListener("change", mqListener);
			}
		});
	}
	unbindMediaListeners(){
		this.listenedMedia.forEach(listener =&gt; {
			window.matchMedia(listener.media).removeEventListener("change", listener.handler);
		});
	}
	getPrevSiblings(elem){
		let siblings = [];
		function step(newElem){
			if(newElem.previousElementSibling){
				siblings.push(newElem.previousElementSibling);
				step(newElem.previousElementSibling);
			}
		}
		step(elem);
		return siblings;
	}
	previousSiblingIsPlaying(elem, currentSrc) {
		let prevSiblings = this.getPrevSiblings(elem);
		let priorSiblingIsPlaying = false;
		prevSiblings.forEach(el =&gt; {
			if (el.src === currentSrc) {
				priorSiblingIsPlaying = true;
			}
		});
		return priorSiblingIsPlaying;
	}
	reloadVideo(){
		this.reloadQueued = true;
		const currentTime = this.video.currentTime;
		const playState = this.video.playState;
		this.video.load();
		const videoLoaded = () =&gt; {
			this.video.playState = playState;
			this.video.currentTime = currentTime.toString();
			this.reloadQueued = false;
			this.video.removeEventListener("loadeddata", videoLoaded);
		};
		this.video.addEventListener("loadeddata", videoLoaded);
	}
}

customElements.define("responsive-video", ResponsiveVideo);
</code></pre>
<p>And it works! In about 50 lines of code, we’ve used a web component to extend HTML video to make it do much more.</p>
<h2>Check out the Demo Page</h2>
<p>You can try a demo of this responsive video web component here.</p>
<p><a href="https://scottjehl.com/sandbox/video-media/reallyrwdvideo.html">Even Responsiver Video Demo Page</a></p>
<p>But hey! Not so fast....</p>
<p>I still have another important thing to cover before this thing is ready to ship.</p>
<h2>Enhancing Reponsibly</h2>
<p>As I mentioned early in the post, the behavior we added with this component is something that many folks <em>expect</em> to already work when they start using responsive video. While there is no current proposal or plans for implementing this in web standards and browsers (that I know of, at least), this does seem like the sort of feature that could possibly become natively supported in browsers one day. I hope it does actually!</p>
<p>If that does ever happen, I really wouldn't want this web component to be duplicating, or worse interfering with the behavior that a browser handles itself. I'd like this web component to act as a polyfill for this behavior if it's not already supported, but if it is supported I want this web component to make itself obsolete.</p>
<p>So just to be safe, we should check if the browser handles this behavior natively before we add it ourselves.</p>
<p>It's time for a feature test!</p>
<h3>Writing a Video Media Change Feature Test</h3>
<p>I created the following feature test to check whether a browser performs this media switching behavior natively. It's somewhat similar to <a href="https://github.com/web-platform-tests/wpt/pull/42361/commits/a2851bd8017fe88b5f391c1bb99c2486f27162cc">the test that I recently patched in the Web Platform Test suite</a> which modern browsers run as part of their builds to ensure they support standard responsive video behavior. Except that this one tests for behavior that is not currently standard, of course.</p>
<p>The test returns a promise that will quickly resolve to either <code>true</code> or <code>false</code> to describe if a browser supports changing video sources after page load, when source media changes.</p>
<pre><code data-sanitized-class="language-js">// feature test for native video media switching media
const videoMediaChangeSupport = async () =&gt; {
	return new Promise(resolve =&gt; {
		const iframe = document.createElement("iframe");
		const video = document.createElement("video");
		const source = document.createElement("source");
		const mediaSource = new MediaSource();
		mediaSource.addEventListener("sourceopen", () =&gt; resolve(true));
		source.src = URL.createObjectURL(mediaSource);
		source.media = "(min-width:10px)";
		video.append(source);
		iframe.width = "5";
		iframe.style.cssText = `position: absolute; visibility: hidden;`;
		document.documentElement.append(iframe);
		iframe.contentDocument.body.append(video);
		setTimeout(() =&gt; { iframe.width = "15"; });
		setTimeout(() =&gt; {
			iframe.remove();
			resolve(false);
		}, 1000);
	});
};
</code></pre>
<p>In case you're curious, the test works by creating a <code>video</code> element with a child <code>source</code> element that has a media query, and appends that video to a generated <code>iframe</code> which is too narrow for the media query to match. Then, it resizes the iframe so that the relevant media query <em>will</em> match. If the video requests the source after the iframe is resized, we can trust that the browser is already handling media switching in video, so we don't need to apply our web component at all!</p>
<h3>Conditional Web Components</h3>
<p>Lastly, it's time to re-define our web component ONLY if that feature test happens to fail. I'm calling this pattern a <em>Conditional Web Component</em>, because everything seems to have cool names now.</p>
<p>To make it conditional, the last line of our component will now look like this:</p>
<pre><code data-sanitized-class="language-js">if( await videoMediaChangeSupport() === false ){
	customElements.define("responsive-video", ResponsiveVideo);
}
</code></pre>
<p>And again, that demo page has this latest logic baked in:</p>
<p><a href="https://scottjehl.com/sandbox/video-media/reallyrwdvideo.html">Even Responsiver Video Demo page</a></p>
<h2>Using ResponsiveVideo.js</h2>
<p>The script I've described is on Github, licensed open source under MIT. Feel free to use it, fork it, or send me issues to consider! And I'll plan publish it to NPM soon as well.</p>
<p>Github Repo for <a href="https://github.com/scottjehl/ResponsiveVideo">ResponsiveVideo</a></p>
<h2>Thanks!</h2>
<p>Thank you for reading. Feel free to reach out on <a href="https://mstdn.social/@scottjehl">Mastodon</a> or via the <a href="https://scottjehl.com/#contact">contact form</a> if you have ideas or questions.</p>
<p>This article was also posted on <a href="https://calendar.perfplanet.com/2023/extending-responsive-video-with-html-web-components/">The Web Performance Calendar</a> so feel free to add to the discussion over there!</p>


guid: https://htmhell.dev/adventcalendar/2023/13/
title: HTML: The Bad Parts
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/13/
hash: �t������6P���
attributes: {"enclosures":[]}
content:
by Mayank<br><p>You've probably heard statements along the lines of "HTML is already accessible by default" or "You don't need to reinvent this perfectly fine HTML control". I consider these to be more of general claims rather than universal truths. It's extremely important for web developers to recognize gaps in the platform. To that end, I've decided to collect a few instances where HTML falls short, through accessibility issues or usability issues.</p>
<p>This is not a comprehensive list, and it does not include gaps in ARIA. I wanted to find a balance between widely known issues and more frequently encountered (but lesser known) ones, while making sure to include some things that we take for granted. In each section, I will include its severity, alternate suggestions, and links where you can find more detailed information.</p>
<h2 data-sanitized-id="lessselect-multiplegreater"><code>&lt;select multiple&gt;</code></h2>
<p>Let's start with an easy one. The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#multiple"><code>multiple</code></a> attribute on <code>&lt;select&gt;</code> <a href="https://www.24a11y.com/2019/select-your-poison-part-2/#:~:text=around%20this%20behavior.-,Multi-select,-Multi-select%20Variant">should pretty much never be used</a>. It's like the polar opposite of single <code>&lt;select&gt;</code>, where instead of universal familiarity, it has universal unfamiliarity. Perhaps its only saving grace is that I have yet to encounter this attribute in any codebase.</p>
<p>To quote a screen-reader user (from Sarah Higley's excellent article linked below):</p>
<blockquote>This is a broken listbox. This is entirely broken, I don’t see any way of doing anything with this.</blockquote>
<p><strong>Impact</strong>: High. There is a good chance most of your users will not know how to navigate this monstrous control.</p>
<p><strong>What to use instead</strong>: A list of checkboxes is a totally fine alternative; or you could build a custom multi-selectable listbox.</p>
<p><strong>Further reading</strong>: <a href="https://www.24a11y.com/2019/select-your-poison-part-2"><code>&lt;select&gt;</code> your poison part 2: test all the things</a> (article by Sarah Higley).</p>
<h2 data-sanitized-id="lessigreater-(for-icon)"><code>&lt;i&gt;</code> (for icon)</h2>
<p>Let's do another easy one to warm up. The <code>&lt;i&gt;</code> element is <a href="https://w3c.github.io/html-aam/#el-i">semantically insignificant</a>, so it's not necessarily “bad” if used for regular text content. More frequently though, it is used for icon fonts.</p>
<p>I wouldn't even normally include this in the list, since this is more of a developer error than a platform issue. However, it is 2023 and I still regularly encounter the <code>&lt;i&gt;</code> element being used with something like FontAwesome. This is problematic mainly because it involves using a web font to visually substitute text characters (which may have one meaning) with icons (which are completely unrelated to the text characters). As a result, the text characters, even though they are generated in CSS, are part of the page's content and will be included in the accessibility tree by default. In addition to that, it looks bad, breaks often (and in <a href="https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/#they-encroach-on-emoji-turf">hilarious ways</a> 🐴), and leads to other accessibility issues. You can learn more about all of these issues in Tyler Sticka's post linked below.</p>
<p><strong>Impact</strong>: Low to medium. You could make it less inaccessible by excluding it from the accessibility tree (using <code>aria-hidden</code>) and providing a text alternative (using <a href="https://www.tpgi.com/the-anatomy-of-visually-hidden/">visually hidden</a> text), but it will still remain problematic.</p>
<p><strong>What to use instead</strong>: SVGs! They are well supported, and better in many important ways. Unlike icon fonts, SVGs are properly designed to display graphics, so they will not result in unexpected text being added to the accessibility tree. There are tons of different ways to use them too, so you can have your pick, whether you like inlining, using sprites, or keeping it within CSS (using <a href="https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images"><code>mask</code></a>).</p>
<p>When using <code>&lt;svg&gt;</code> elements, a reliable pattern is to use <code>aria-hidden</code> alongside an additional text alternative (which may be visually hidden if needed). For example, the markup for a button might look like this:</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>svg</span> <span data-sanitized-class="token attr-name">aria-hidden</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>true<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>use</span> <span data-sanitized-class="token attr-name">href</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>#heart<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>svg</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>span</span><span data-sanitized-class="token punctuation">&gt;</span></span>Favorite<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>span</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>button</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p><strong>Further reading</strong>: <a href="https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/">Seriously, Don't Use Icon Fonts</a> (article by Tyler Sticka)</p>
<h2 data-sanitized-id="lessulgreater-and-lessolgreater"><code>&lt;ul&gt;</code> and <code>&lt;ol&gt;</code></h2>
<p>I can't make a list (heh!) of problematic HTML and not include lists in there.</p>
<p>I'm not going to say “don't use lists”; lists are very useful in many scenarios. But developers have a tendency to <a href="https://almanac.httparchive.org/en/2022/markup#top-elements">overuse lists</a>, often just to remove list styles anyway. Sometimes we may even slap an ARIA role on it (thus overriding the list semantics), or worse, add non-<code>&lt;li&gt;</code> children for styling purposes or otherwise (thus producing invalid markup). In all of these scenarios, we are losing any benefit provided by list elements, and potentially introducing new issues.</p>
<p>Safari has noticed this overuse of lists, dubbing it "list-itis", and has started removing list semantics in certain cases; lists that are outside a <code>nav</code> and are styled with <code>list-style: none</code> are <em>not</em> considered to be lists by Safari. While this change was surely done with good intentions, it has the unfortunate effect of making even legitimately styled lists purely presentational.</p>
<p>In addition to all of this, ordered lists have their own oddities. Browsers consider the item numbers to be "presentational", so when selecting and copying a list, the numbers are not included in the plain-text clipboard entry. This breaks user expectations, especially when the numbers are important, as in the case of legal documents.</p>
<p><strong>Impact</strong>: Low to medium. I think it's totally fine to let Safari/VoiceOver do its thing; in fact, their users are likely used to it and may even expect an almost list-free web. The clipboard issue with ordered lists can be quite serious, but there are workarounds.</p>
<p><strong>What to use instead</strong>: If you really need to preserve list semantics, <code>role="list"</code> can be added to <code>&lt;ul&gt;</code>. If you're already using a different role, then plain <code>&lt;div&gt;</code>s should work fine. <a href="https://benmyers.dev/blog/on-the-dl/"><code>&lt;dl&gt;</code></a> is another potential alternative for key-value pairs.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>ul</span> <span data-sanitized-class="token attr-name">role</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>list<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>li</span><span data-sanitized-class="token punctuation">&gt;</span></span>…<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>li</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>li</span><span data-sanitized-class="token punctuation">&gt;</span></span>…<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>li</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line">	<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>li</span><span data-sanitized-class="token punctuation">&gt;</span></span>…<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>li</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>ul</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p>If the list item numbers are important, consider using unordered lists where the numbers are part of the text content.</p>
<p><strong>Further reading</strong>: <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">"Fixing" Lists</a> (article by Scott O'Hara), <a href="https://threadreaderapp.com/thread/1337226933822603270.html">Twitter thread</a> by Apple's James Craig, and <a href="https://siderea.dreamwidth.org/1819759.html">"I Blame the W3C's HTML Standard for Ordered Lists"</a> (article by Sibylla Bostoniensis).</p>
<h2 data-sanitized-id="title-attribute"><code>title</code> attribute</h2>
<p>The <code>title</code> attribute is the one that frustrates me the most, particularly because of its long history. It is so bad that the <a href="https://www.w3.org/TR/html/dom.html#the-title-attribute">HTML spec</a> explicitly warns against using it. And yet I've seen this attribute used in almost every codebase I've worked with.</p>
<p>Some of the many issues with <code>title</code> include:</p>
<ul>
<li>Sighted keyboard users have absolutely no way to access the <code>title</code> content.</li>
<li>Touch-screen users also cannot access the <code>title</code> content.</li>
<li>The default tooltip styling cannot be changed in any way.</li>
<li>The trigger needs to be hovered for an arbitrarily long delay before it appears.</li>
<li>The tooltip cannot be dismissed without moving the pointer, and therefore can obscure other parts of the page.</li>
<li>The tooltip cannot be hovered over, meaning the mouse needs to keep hovering the trigger element. As a result, the tooltip could become obscured by the mouse pointer, its text will not be selectable, and screen-magnifier users may find it impossible to view it.</li>
<li>The text inside the tooltip does not scale with the user's zoom level.</li>
<li>Long text inside the tooltip cannot be made to wrap; in some browsers, it will become as wide as the screen itself (even escaping the browser window bounds) before it starts wrapping.</li>
<li>There is no way to indicate if the <code>title</code> content should be part of the element's accessible name or description (or neither).</li>
<li>Screen-readers do not consistently announce the <code>title</code> content on all elements.</li>
</ul>
<p><strong>Impact</strong>: High. By relying on this attribute, you are making the intentional choice of leaving out a large percentage of your users.</p>
<p><strong>What to use instead</strong>: In many cases, including the text as part of the element's content can suffice; this text could be visually-hidden if necessary. In other cases, <code>aria-labelledby</code> or <code>aria-describedby</code> will work better than <code>title</code>. One exception is iframes, which need to be labeled using <code>title</code>.</p>
<p>In almost all cases, a <a href="https://www.mayank.co/blog/tooltip-using-webcomponents">custom tooltip</a>, in addition to the techniques described above, would also work better than <code>title</code>. However, tooltips come with their <a href="https://adamsilver.io/blog/the-problem-with-tooltips-and-what-to-do-instead/">own set of problems</a>, so an even better solution would be to redesign the interface to avoid needing tooltips altogether. For example, you could <em>always</em> show the text or use a <a href="https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/">disclosure pattern</a>.</p>
<p><strong>Further reading</strong>: <a href="https://www.24a11y.com/2017/the-trials-and-tribulations-of-the-title-attribute/">The Trials and Tribulations of the Title Attribute</a> (article by Scott O'Hara), and <a href="https://www.tpgi.com/using-the-html-title-attribute/">Using the HTML title attribute</a> (article by Steve Faulkner).</p>
<h2 data-sanitized-id="lessdatalistgreater"><code>&lt;datalist&gt;</code></h2>
<p>At first, the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"><code>datalist</code> element</a> sounds like a promising alternative to custom comboboxes. The more you use it though, the more disappointed you'll find yourself.</p>
<ul>
<li>This element has very questionable default styling.
<ul>
<li>On Chrome desktop, it appears as a weirdly offset popover that can drastically shift its position while the user is typing; at the same time, it becomes detached from the input when the user scrolls away.</li>
<li>On Chrome Android, it appears above the virtual keyboard (rather than on the page) and only in portrait mode.</li>
</ul>
</li>
<li>Its appearance cannot be customized at all. It does not even respect the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme"><code>color-scheme</code></a>.</li>
<li>It does not respect the user's font size or zoom level.</li>
<li>It generally has good support on desktop screen-readers, but on mobile it requires navigating to the very end of the page to interact with any options.</li>
<li>It does not work on Firefox Android at all!</li>
</ul>
<p><strong>Impact</strong>: Medium to high. At best, this element can be useful as an optional hint for input fields that would still be perfectly usable without the added help from autocompleting options.</p>
<p><strong>What to use instead</strong>: <code>&lt;select&gt;</code> or a custom combobox. 🤷</p>
<p><strong>Further reading</strong>: <a href="https://adrianroselli.com/2023/06/under-engineered-comboboxen.html">Under-Engineered Comboboxen?</a> (article by Adrian Roselli)</p>
<h2 data-sanitized-id="lessinput-placeholdergreater"><code>&lt;input placeholder&gt;</code></h2>
<p>Placeholder text is intended to <em>help</em> the user by providing a hint when an input is empty. In practice, it often ends up doing the opposite.</p>
<p>Placeholder text is usually prone to color contrast issues. If you try fixing color contrast, it may end up looking indistinguishable from real input values. If you put important information in it (like password requirements, or god forbid, the label itself), the input value will hide this important information as soon as the user starts typing.</p>
<p><strong>Impact</strong>: Medium to high. At best, it may be ignored. At worst, it may frustrate your users and impact your revenue.</p>
<p><strong>What to use instead</strong>: Nothing? A prominently visible label may be sufficient. If you do need hint text, consider placing it outside the input, and associating it using <code>aria-describedby</code>.</p>
<pre data-sanitized-class="language-html"><code data-sanitized-class="language-html"><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>label</span> <span data-sanitized-class="token attr-name">for</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>phone<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Phone number<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>label</span><span data-sanitized-class="token punctuation">&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>input</span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>phone<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token attr-name">aria-describedby</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>phone-hint<span data-sanitized-class="token punctuation">"</span></span> <span data-sanitized-class="token punctuation">/&gt;</span></span></span><br><span data-sanitized-class="highlight-line"><span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;</span>div</span> <span data-sanitized-class="token attr-name">id</span><span data-sanitized-class="token attr-value"><span data-sanitized-class="token punctuation attr-equals">=</span><span data-sanitized-class="token punctuation">"</span>phone-hint<span data-sanitized-class="token punctuation">"</span></span><span data-sanitized-class="token punctuation">&gt;</span></span>Example: 123-456-7890<span data-sanitized-class="token tag"><span data-sanitized-class="token tag"><span data-sanitized-class="token punctuation">&lt;/</span>div</span><span data-sanitized-class="token punctuation">&gt;</span></span></span></code></pre>
<p><strong>Further reading</strong>: <a href="https://www.smashingmagazine.com/2018/06/placeholder-attribute/">Don't Use The Placeholder Attribute</a> (article by Eric Bailey)</p>
<h2 data-sanitized-id="lessinput-type=numbergreater"><code>&lt;input type=number&gt;</code></h2>
<p>Let's cut to the chase: number inputs will increment/decrement when using scroll wheel or gesture or arrow keys, making it extremely prone to accidents. Yikes!</p>
<p>And that's in addition to its other accessibility, consistency, and styling issues.</p>
<ul>
<li>Some browsers will silently discard non-numeric user input without any feedback.</li>
<li>Other browsers will allow entering arbitrary numbers but fire events with empty values, thus losing user-entered data.</li>
<li>The "spin" buttons are extremely small and difficult to click.</li>
<li>The "spin" buttons cannot be styled (beyond simple color scheme).</li>
</ul>
<p><strong>Impact</strong>: High. The scrolling issue alone is enough to make this a dealbreaker, as users could <a href="https://bradfrost.com/blog/post/you-probably-dont-need-input-typenumber/">enter the wrong number without even realizing</a>.</p>
<p><strong>What to use instead</strong>: <code>&lt;input inputmode="numeric" pattern="[0-9]*"&gt;</code>. The <a href="https://better-mobile-inputs.netlify.app/"><code>inputmode</code></a> attribute helps show a nicer keyboard on touchscreen devices, and the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern"><code>pattern</code></a> attribute helps with validation.</p>
<p><strong>Further reading</strong>: <a href="https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/">Why the GOV.UK Design System team changed the input type for numbers</a></p>
<h2 data-sanitized-id="lessinput-type=dategreater"><code>&lt;input type=date&gt;</code></h2>
<p>Native date pickers have many browser inconsistencies, with some parts of them being inaccessible.</p>
<ul>
<li>On Chrome desktop, the calendar button cannot be tabbed to.</li>
<li>On Safari desktop, the date picker cannot be closed using keyboard.</li>
<li>On Safari desktop, the date picker is minuscule and cannot be scaled up.</li>
<li>On Chrome and Firefox Android, the date input cannot be typed into, forcing the use of the clunky calendar UI even if the date is familiar to the user.</li>
</ul>
<p>In all environments, the date format is shown as placeholder text which, as we've already established above, is problematic. The browser determines the date format, and it may choose something strange (like the US date format), leading to unnecessary confusion.</p>
<p><strong>Impact</strong>: High. These inconsistencies are not just minor differences, they introduce real barriers to access.</p>
<p><strong>What to use instead</strong>: Honestly, use simple text inputs, or even selects in some cases.</p>
<p>You can also use separate inputs for date/month/year and group them with a fieldset. This fieldset could even be styled to look like <a href="https://russmaxdesign.github.io/date-picker-input/">one contiguous form field</a>. If you choose to hide the input labels though, I would also suggest showing the date format outside the inputs (as part of the legend or below the inputs).</p>
<p>Custom date pickers are notoriously hard to get right, so if you choose to one, it should be optional and in addition to the text inputs.</p>
<p><strong>Further <s>reading</s> watching</strong>: <a href="https://www.youtube.com/watch?v=D2Gy2WN4Iys">What makes an accessible date picker? Is it even possible?</a> (talk by Russ Weakley)</p>
<h2 data-sanitized-id="lessmenugreater"><code>&lt;menu&gt;</code></h2>
<p>Just don't. Whatever you expect this element does… it probably doesn't. The <code>&lt;menu&gt;</code> element was originally intended to be a "context menu" but it never got anywhere, and now it is replaced by <code>&lt;ul&gt;</code>.</p>
<p><strong>Impact</strong>: Low. <code>&lt;menu&gt;</code> is simply remapped to <code>&lt;ul&gt;</code>.</p>
<p><strong>What to use instead</strong>: Because the term “menu” is so overloaded, it depends on what you're trying to accomplish. Some alternatives include:</p>
<ul>
<li><code>&lt;ul&gt;</code>(or <code>role="list"</code>) when you have a list.</li>
<li><code>role="menu"</code> when you have a desktop-like menu.</li>
<li><code>role="listbox"</code> when you have something resembling a custom <code>&lt;select&gt;</code>.</li>
<li><code>&lt;dialog&gt;</code> or <code>role="dialog"</code> when you have a generic popover (perhaps with a <code>role="list"</code> inside).</li>
</ul>
<p>Do note that some of these roles require custom JavaScript interactions to be wired up.</p>
<p><strong>Further reading</strong>: <a href="https://adrianroselli.com/2023/05/be-careful-using-menu.html">Be Careful Using 'Menu'</a> (article by Adrian Roselli)</p>
<h2 data-sanitized-id="lessbutton-disabledgreater"><code>&lt;button disabled&gt;</code></h2>
<p>Disabled buttons are strangely overused, perhaps due to their familiarity. Two common scenarios for disabled buttons include:</p>
<ul>
<li>when an action is temporarily unavailable, perhaps due to requiring some prerequisite task (such as selecting an item or filling a form correctly).</li>
<li>when submitting a form, to prevent duplicate submissions.</li>
</ul>
<p>However, the <code>disabled</code> attribute does more than just prevent clicks. It also prevents hover and focus capabilities. This has many negative ramifications. Disabled buttons cannot show tooltips (which may be important for explaining why the button is disabled). Keyboard users cannot <kbd>Tab</kbd> to disabled buttons. If a button suddenly becomes disabled (e.g. when submitting a form), focus could get lost and create a confusing experience. Additionally, disabled buttons are exempt from <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">color contrast requirements</a>, increasing the likelihood that it will have poor contrast.</p>
<p><strong>Impact</strong>: High. Disabled buttons selectively serve a small percentage of your user-base (and not very well either).</p>
<p><strong>What to use instead</strong>: Use <code>aria-disabled="true"</code> and manually disable clicks. If possible though, consider reworking the design to avoid needing disabled buttons altogether.</p>
<p><strong>Further reading</strong>: <a href="https://css-tricks.com/making-disabled-buttons-more-inclusive/">Making Disabled Buttons More Inclusive</a> (article by Sandrina Pereira)</p>
<h2 data-sanitized-id="lessvideogreater"><code>&lt;video&gt;</code></h2>
<p>The native video player still has many accessibility and usability issues. For keyboard users, focus is a recurring issue across browsers, because not all controls can be reached, and the controls disappear once the video starts playing. For screen reader users, the experience is lacking in some areas, especially for more "advanced" features like picture-in-picture mode.</p>
<p>I highly recommend checking out Adrian's article below for a deep-dive on support across various browsers and screen-reader pairings.</p>
<p><strong>Impact</strong>: Medium to high.</p>
<p><strong>What to use instead</strong>: Unsure. Maybe a custom video player, if it's built with accessibility in mind. Regardless of what you use, please include captions and a transcript, and provide a way to download the video for offline viewing in the user's player of choice. Oh and please for the love of your users, <a href="https://www.boia.org/blog/why-autoplay-is-an-accessibility-no-no">do not use <code>autoplay</code></a>.</p>
<p><strong>Further reading</strong>: <a href="https://adrianroselli.com/2023/09/browser-video-players-review.html">Browser Video Players Review</a> (article by Adrian Roselli)</p>
<h2 data-sanitized-id="link-protocols-(tel-mailto)">Link protocols (<code>tel:</code>, <code>mailto:</code>)</h2>
<p>Links on the web can have a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler/Web-based_protocol_handlers">non-http protocol</a> like <code>mailto:</code> or <code>tel:</code>. These protocols are commonly used as a supposed way to help the users (or spammers) send an email or dial a number. However, this makes a very bold assumption is that the user is visiting the webpage on the same device that will be used to send the email or dial a number and wants to use the default application to do so. This can lead to frustration when the user is just looking for a way to copy the number or dial it from a different device. To make it worse, the actual address/number often gets hidden underneath the link, instead displaying something useless like "Email me".</p>
<p><strong>Impact</strong>: Low to medium. It's confusing and annoying, but not the end of world I suppose. Users will (reluctantly) find workarounds.</p>
<p><strong>What to use instead</strong>: Just show the actual email address or phone number. It is trivial to copy or manually transfer it over to the desired device. Additionally, many devices will automatically display a prompt when selecting text that can be recognized as an email/number.</p>
<p><strong>Further reading</strong>: <a href="https://bkardell.com/blog/tel-me.html">tel:me.about.it.</a> (article by Brian Kardell)</p>
<hr>
<h2 data-sanitized-id="wrapping-up">Wrapping up</h2>
<p>In closing, I just want to reiterate that accessibility is not as simple as "just use HTML". Even with best intentions and pristine HTML source, we may end up with inaccessible UIs if we don't do our due diligence.</p>
<p>I'm sure this list will look different (hopefully shorter) 10 years from now. As more people start to take interest in these things, it will create more awareness. It will push standards bodies and browser vendors to improve the platform and get us closer to a more "accessible by default" web. You can personally help move the needle by opening new bugs or by commenting on existing ones:</p>
<ul>
<li>Chrome/Blink: <a href="https://bugs.chromium.org/p/chromium/">https://bugs.chromium.org/p/chromium/</a></li>
<li>Firefox/Gecko: <a href="https://bugzilla.mozilla.org/">https://bugzilla.mozilla.org/</a></li>
<li>Safari/WebKit: <a href="https://bugs.webkit.org/">https://bugs.webkit.org/</a></li>
</ul>


guid: https://scottjehl.com/posts/polyfilling-with-wcs/
title: Conditional Web Components are Perfect for Polyfills
author: ;Scott Jehl
link: https://scottjehl.com/posts/polyfilling-with-wcs/
hash: D���3�r�m �#���C
attributes: {"enclosures":[]}
content:
<p>Yesterday, <a href="https://scottjehl.com/posts/even-responsiver-video/">I released</a> an <a href="https://github.com/scottjehl/ResponsiveVideo/">HTML Web Component that makes responsive HTML <code>video</code> a little responsive-r</a>. In short, the new (standard!) behavior of responsive video is great to have (<a href="https://scottjehl.com/posts/using-responsive-video/">and I'm a fan</a>), but it falls a little short of many folks' expectations for truly responsive web design because it's only "responsive" at initial page load, and not after that. My Web Component patches that behavior, but only after checking that the browser doesn't already handle it on its own.</p>
<h2>Planning for Obsolescence</h2>
<p>In the article about the component, I called the pattern a <em>Conditional Web Component</em>, because depending on the browser's feature support, that web component might do something or nothing at all. To be clear, I'm not aware of any current plans for this behavior to be implemented natively in browsers. But it <em>feels</em> enough like the sort of thing that might be, one day, so I wanted to make sure my component plans for its own obsolescence accordingly. Indeed, I hope this behavior does work its way into browsers one day and my component can be yanked from delivery, but at the very least I want to make sure it does no harm if it's still there.</p>
<h2>Making It Conditional</h2>
<p>In my code, that condition happens at the end of the component script, right where the custom element gains its web component behavior. I wrote a feature test to see if the browser supports the functionality that the component will add (that test is an asynchronous function called <code>videoMediaChangeSupport</code>, <a href="https://github.com/scottjehl/ResponsiveVideo/blob/main/responsivevideo.js#L63">which you can view here</a>), and I call that function to decide whether to define the custom element's behavior or not.</p>
<p>In my case, it looks like this:</p>
<pre><code data-sanitized-class="language-js">if( await videoMediaChangeSupport() === false ){
	customElements.define("responsive-video", ResponsiveVideo);
}
</code></pre>
<p>Now, that <code>await</code> bit is not important to the pattern itself, to be clear. Mine happened to be an asynchronous function, but it could just as easily look something like this:</p>
<pre><code data-sanitized-class="language-js">if( myFeatureIsNotSupported ){
	customElements.define("responsive-video", ResponsiveVideo);
}
</code></pre>
<p>Anyway, using web components to write a polyfill in this way feels novel and appropriate to me, particularly for HTML elements. I'm interested in hearing if anyone else finds the same!</p>


guid: https://htmhell.dev/adventcalendar/2023/14/
title: Getting started with Web Performance 🚀
author: ;Manuel Matuzovic
link: https://htmhell.dev/adventcalendar/2023/14/
hash: �[İ��,�/���,*#�
attributes: {"enclosures":[]}
content:
by Alistair Shepherd<br><p>
  </p><figure>
    <a href="https://htmhell.dev/images/advent2023/web-performance/www-sheep.jpg">
      <picture>
        <source type="image/avif">
        <source type="image/webp">
        <img alt="Illustration of a sheep eating grass with 'www.'' written on its side. Someone is observing it at a distance through binoculars" src="https://htmhell.dev/images/advent2023/web-performance/www-sheep-740.jpg" width="1000" height="697" loading="eager" fetchpriority="high">
      </picture>
    </a>
    <figcaption>Carefully observing websites in the wild</figcaption>
  </figure>
<p></p>
<p>As the murderous tortoises start to converge on Ryūji’s hideout, they pull out their phone. It’s a cheap, older device but it’s survived the toils of the tortoise-ageddon well so far. Thankfully the internet still exists, although a bit slower, so they’re able to search online for how to scare tortoises away. The first result looks promising so they quickly tap through to find the information they need. But as the loading bar moves at barely a crawl, Ryūji realises with horror that this website is loading far too slowly—20 seconds have passed and it’s still just a white screen! They look up and realise they’ve run out of time, poor website performance was Ryūji’s downfall.</p>
<p>As Ryūji discovered, web performance really matters, and is about making websites fast. I hope that most of us don’t encounter as drastic circumstances as they have! For the rest of us a well performing website is more accessible to people, offers a more enjoyable experience, and may even encourage more views, shares or sales.</p>
<p>But web performance is also quite tricky! Let us go through some of the reasoning, jargon, metrics, tools, and some top tips on how you can improve the performance of websites you look after. Whether you’re a practised performance prophet, dipping your toes into the river of load times, or are desperately hoping someone will explain what an FCP is and why Google wants one, let’s all dig into web performance together!</p>
<p>We’ll be diving into the river of load times and exploring what web performance is, why it’s important, how to measure it and finally my click-baity “<strong>Ten Wild Web Performance Tips! You’ll be saving number 5 for later!</strong>”. If you already know your CLS’ from your FCPs, lab from field data, and are well familiar with Lighthouses (not the ones with big lights) then you can <a href="https://htmhell.dev/adventcalendar/2023/14/#tips">jump straight to the tips</a>.</p>
<p>Before we start getting into it, there’s one thing I want to make super clear especially if you haven’t spent a lot of time with web performance: Web performance can be pretty tricky. I mention this so you don’t get demoralised if you hit a wall or struggle to identify an issue as we’re going to fill your performance toolbox with some great metrics, strategies, tools, and tips. These will help you make some real improvements, but know that even with years of experience some things are still a challenge to find! With our toolbox you’ll find that developers, designers, and even managers can each make a meaningful impact on website performance without necessarily being crowned Speed Supremo.</p>
<h2 data-sanitized-id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#why">Why web performance is important, why should we care?</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#look">What does web performance look like?</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#metrics">Performance science with Metrics</a>
<ol>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#cwv-crux">Core Web Vitals and the Chrome User Experience report</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#lcp">LCP — Largest Contentful Paint</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#inp">INP — Interaction to Next Paint</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#cls">CLS — Cumulative Layout Shift</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#bonus-metrics">Some bonus metrics if you’re keen</a></li>
</ol>
</li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#testing">Testing 101</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#tips">Ten Wild Web Performance Tips!</a></li>
<li><a href="https://htmhell.dev/adventcalendar/2023/14/#what-next">What next?</a></li>
</ol>
<h2 data-sanitized-id="why">Why web performance is important, why should we care?</h2>
<p>Web performance can often be overlooked, and that’s generally a big mistake. It’s easy to put it on the backlog, assume your development tooling is solving it for you, or think that modern hardware and networks remove the need completely.</p>
<p>But performance is just as important as it ever has been, perhaps even more so! More people than ever are online, relying on the web for crucial services, yet the gap in hardware between the best iPhone and cheapest smartphone has never been more extreme. For that reason, web performance isn’t just a tech problem to solve, it’s a social issue. Our societies require people to use websites, yet we serve 10MB poorly-optimised JavaScript-heavy ‘apps’ via metered connections to a $30 smartphone—that’s never going to work!</p>
<p>Phew, I got a bit serious there! To reiterate, building a website that performs well is important not just for SEO and sales, but also to open the web to more people making it more inclusive and accessible to all.</p>
<p>On a personal level, I travel on trains quite a bit and there’s nothing like free train WiFi or spotty 3G or 4G in rural areas for showing up websites with poor performance (at least in the UK!). When one site takes 5 seconds to load and another takes 50 seconds, I know which one I’d prefer to use.</p>
<p>If you need more convincing, web performance can often be directly correlated to business metrics like traffic, conversions, and sales. <a href="https://wpostats.com/">WPO Stats</a> lists fantastic performance-related Case Studies if you need material to send to your boss or client. Google is also using web performance as a ranking factor in its algorithms—I’ve seen performance improvements increase impressions from Google Search by 300%. That’s nothing to scoff at!</p>
<h2 data-sanitized-id="look">What does web performance look like?</h2>
<p>Simply put, web performance is how fast a website loads and feels, and how fast it feels while using it.</p>
<p>There are a few different factors that we’ll get into, but a website that takes a second to go from clicking the link to a fully complete page is pretty quick. On the other hand a slow page might have a long loading screen, parts missing for ages after load, or take a second to react to every click.</p>
<video width="740" height="208" controls="controls" muted="" playsinline="" preload="none">
  <source src="https://htmhell.dev/images/advent2023/web-performance/general-740.mp4" type="video/mp4">
</video>
<details>
  <summary>Video description</summary>
<p>The video shows two websites loading side-by-side, with a timer below each of them. The page they're loading is visually identical at the end, but they load differently.</p>
<p>The page itself features the HTMHell logo, a title of "HTMHell's Handsome Hat House", a couple of 'article' images, some placeholder content and a large image of hats filling half of the page.</p>
<p><strong>On the left</strong> the page loads in with unstyled text after 1 second. As images slowly load in top-to-bottom, they move the other page content around. After 13 seconds the headings increase in size and change style, which after a further 2 seconds is replaced with a custom font. The page finishes loading at 15 seconds.</p>
<p><strong>On the right</strong> the page loads in a lot faster, and without any images moving content around. The images and font load in significantly earlier, and the page has finished loading at 4.8 seconds.</p>
</details>
<p><a href="https://htmhell.dev/images/advent2023/web-performance/general.mp4">Full size video</a></p>
<h2 data-sanitized-id="metrics">Performance science with Metrics</h2>
<p>Now we know what web performance feels like! Unfortunately feeling is notoriously a not great™ way of comparing things, and humans unfortunately haven’t evolved the ability to instinctively determine web performance just yet. I have been trying to hone my sense of Time To First Byte but am yet to see positive results.</p>
<p>There’s some good news though, the web performance and development community have come up with some fantastic tests, metrics, and analysis methods to help us measure, understand, and compare performance. These make it easy to quantify improvements, set measurable goals and find out where websites can be improved.</p>
<p>I am back with the bad news though: there’s bloody loads of them. They’re a mixture of meaningless jargon and 3 or 4 letter initialisms that will leave you extremely confused and wondering if you’re reading a sci-fi novel. Just take a look at some of this mess:</p>
<p>
  <a href="https://htmhell.dev/images/advent2023/web-performance/wordcloud.png">
    <img alt="Wordcloud with around 50 web-performance related terms. The terms don’t matter, the point is how ridiculously busy and chaotic it is" src="https://htmhell.dev/images/advent2023/web-performance/wordcloud-740.png" width="1000" height="500" loading="lazy">
  </a>
</p>
<p>If you know your web perf and questioned PES and THWIMP, pat yourself on the back. Those ones aren’t real and I’m pulling your leg. PES is obviously Parakeets Eat Seeds and a THWIMP is obviously this cutie from Super Mario World:</p>
<p>
  <img alt="Thwimp from Super Mario World, pixel graphics big rock with spiky edges and a grumpy frown face" src="https://htmhell.dev/images/advent2023/web-performance/thwimp.png" width="64" height="64" loading="lazy">
</p>
<p>In all seriousness however, there are loads of metrics, definitions, and jargon that are so easy to forget or get confused about. I’m going to simplify this down, and give you a glossary and description of only the metrics you really need to know. For the moment this will be a summary, we’ll get into the tips on how to fix them later!</p>
<h3 data-sanitized-id="cwv-crux">Core Web Vitals and the Chrome User Experience report</h3>
<p>If you’ve used Google’s <a href="https://pagespeed.web.dev/">Pagespeed Insights</a> or <a href="https://htmhell.dev/adventcalendar/2023/14/search.google.com/search-console/">Search Console</a> then you may have already heard of Core Web Vitals or the Chrome User Experience report.</p>
<p>Core Web Vitals, initialised to CWV, is a collection of metrics that help to measure web performance in a way that emulates how a user may feel about performance. Google collects and analyses these metrics and a few others to know how fast a website performs. These results can then be used in their search algorithms to reward well-performing websites.</p>
<p>How do they collect those metrics? People navigating with Google Chrome have the performance of the websites they visit anonymously recorded and sent to Google. That’s where the Chrome User Experience Report (CRUX) comes in. CRUX consists of a giant database of the real-world performance of all websites that are visited by a statistically significant number of Chrome users.</p>
<p>You can access <a href="https://developer.chrome.com/docs/crux/">CRUX</a> data through <a href="https://pagespeed.web.dev/">Pagespeed Insights</a>, <a href="https://htmhell.dev/adventcalendar/2023/14/search.google.com/search-console/">Google Search Console</a>, <a href="https://developer.chrome.com/docs/crux/history-api/">Google developer APIs</a>, and using various other tools. You can also <a href="https://developer.chrome.com/docs/crux/bigquery/">query the database itself using Google’s BigQuery</a>, but watch out as that can get expensive quickly!</p>
<p>The three metrics used in Core Web Vitals are some of the best we have to understand how fast a website is for real people, and so those metrics will be what we focus on. You can find lots more information about these metrics and many more at <a href="https://web.dev/explore/learn-core-web-vitals">web.dev</a>, I’d recommend checking that out for more technical info.</p>
<h3 data-sanitized-id="lcp">LCP — Largest Contentful Paint</h3>
<p>“Wow this website is taking a long time to load” we may say, as we click on a link and are left with a blank or sparse screen for what feels like an age. What we’re seeing here is a slow Largest Contentful Paint.</p>
<video width="740" height="208" controls="controls" muted="" playsinline="" preload="none">
  <source src="https://htmhell.dev/images/advent2023/web-performance/lcp-740.mp4" type="video/mp4">
</video>
<details>
  <summary>Video description</summary>
  The video shows two pages loading side-by-side, with a timer below each of them. The page they're loading is visually identical at the end, but they load differently.
  The page itself features the HTMHell logo, a title of "HTMHell Horticultural Heuristics", some placeholder content and a background image of some house plants.
  Both pages load the logo, title and content similarly within 3.5 seconds. The page on the right loads a background image in relatively quickly, appearing all at once at 4 seconds in. The page on the left takes significantly longer to load the image, doing it from top-to-bottom and taking 15.7 seconds before it's completed.
</details>
<p><a href="https://htmhell.dev/images/advent2023/web-performance/lcp.mp4">Full size video</a></p>
<p>LCP measures how long it takes for the largest single piece of content to appear when loading the page. That piece of content could be an image, heading, video, or graphic. For most websites it ends up being a large ‘hero’ image at the top of the page, although this varies per-site.</p>
<p>The goal of LCP is to understand when the main parts of the page load—the parts that the visitor will be most interested in. Your goal should be to load those elements as fast as possible. <a href="https://web.dev/articles/defining-core-web-vitals-thresholds#achievability">Aim for an LCP of 2.5 seconds or less</a>.</p>
<h3 data-sanitized-id="inp">INP — Interaction to Next Paint</h3>
<p>Imagine we’re browsing a website and click a button or link. We expect it to react instantly but instead we’re left waiting. Did it work? Maybe it didn’t go through? Should we click it again? Turns out it was just very slow at reacting to clicks. We’ve encountered poor Interaction to Next Paint in the wild.</p>
<video width="740" height="352" controls="controls" muted="" playsinline="" preload="none">
  <source src="https://htmhell.dev/images/advent2023/web-performance/inp-740.mp4" type="video/mp4">
</video>
<details>
  <summary>Video description</summary>
<p>The video shows a page with HTMHell logo, a title of "HTMHell Humour Hobbies" and a set of accordions.</p>
<p>The recording shows a user trying to interact with the accordion buttons, but it takes a second or two after clicking before the accordion responds, making it a bit confusing as they seemingly randomly open/close.</p>
<p>The accordion titles are a bit silly:</p>
<ul>
<li>What are the HTMHell Humour Hobbies?</li>
<li>Is this entire thing complete nonsense?</li>
<li>Am I good at coming up with placeholder content for demos?</li>
<li>No it appears that I'm probably not actually</li>
</ul>
</details>
<p><a href="https://htmhell.dev/images/advent2023/web-performance/inp.mp4">Full size video</a></p>
<p>How a website appears is a major part of performance, but if everything appears quickly yet takes a second for any of the buttons or links to respond that’s pretty frustrating! Interaction to Next Paint measures the time it takes between the user interacting with a page and the website something on the screen in response.</p>
<p>Websites with lots of JavaScript running in the background or heavy tasks (like network requests) running on clicks may take longer to ‘react’ to a button press. Your goal should be to make sure interactions are as snappy and responsive as you can. Ideally this will be as fast as you can make it, but <a href="https://web.dev/articles/inp#what_is_a_good_inp_score">you should aim for at least within 200ms</a>.</p>
<p><em>Note: Interaction to Next Paint is not yet in Core Web Vitals—<a href="https://web.dev/blog/inp-cwv">it’ll replace the similar First Input Delay (FID) in March 2024</a>. I would recommend focusing on INP already though, you’ll be getting ready and very likely improving FID at the same time!</em></p>
<h3 data-sanitized-id="cls">CLS — Cumulative Layout Shift</h3>
<p>We’re buying a new Microwave online, have just gone through the full checkout process and are about to click confirm. Oh silly us, we’ve accidentally added 10 Microwaves! We go to click the Cancel button, but suddenly an advert appears, shifting the “Confirm” button to where we clicked! Disaster! That’s Cumulative Layout Shift, one of the trickiest and most frustrating performance metrics.</p>
<video width="740" height="494" controls="controls" muted="" playsinline="" preload="none">
  <source src="https://htmhell.dev/images/advent2023/web-performance/cls-740.mp4" type="video/mp4">
</video>
<details>
  <summary>Video description</summary>
<p>The video shows a page with HTMHell logo, a title of "HTMHell Hamburger Heating Hardware", and a product page for a microwave. There's an image of a generic microwave, a title of "Microwave", description "A very good microwave I'm sure" and "Selected quantity ×10". Below are two buttons, one saying "Pay now — $6,000" and another saying "I don't need that many microwaves".</p>
<p>The recording shows a mouse cursor reviewing the page and highlighting the quantity line. They then go and hover over the "I don't need that many microwaves", but just before they click an advert appears above the two buttons. The advert says "YOU NEED MORE BARBECUES BUY BARBECUES AD" and shifts the two buttons down so the user has instead accidentally clicked on the "Pay now — $6,000" button. It changes to "Loading...", the user highlights things in frustration and then the buttons and ad are replaced with a message saying "Success! Enjoy your microwaves".</p>
</details>
<p><a href="https://htmhell.dev/images/advent2023/web-performance/cls.mp4">Full size video</a></p>
<p>Cumulative Layout Shift measures the amount the page shifts around in an unexpected way. Our Microwave example is particularly drastic, where someone ends up doing something they didn’t want to do based on the page shifting. In addition to interactive elements shifting under someone’s cursor, CLS can create extremely frustrating reading experiences where sentences jump around whilst you’re reading them.</p>
<p>CLS is trickier to quantify than LCP and FID. Throughout the user’s visit, every content shift will be given a score based on how much it moved and added up for a ‘cumulative’ total. Lower is better here, so a single small shift may be 0.04 units, whilst a huge advertisement loading late, and pushing content out of the way would be higher at 0.5. <a href="https://web.dev/articles/defining-core-web-vitals-thresholds#achievability_3">You should aim for absolutely no CLS, but less than 0.1 at least</a>.</p>
<h3 data-sanitized-id="bonus-metrics">Some bonus metrics if you’re keen</h3>
<p>There are plenty more metrics, and a few others that are very handy to know!</p>
<p>
  </p><details>
    <summary>Bonus Metrics: FCP, TTFB, SI and FID</summary>
<p><strong>First Contentful Paint</strong> (<a href="https://web.dev/articles/fcp">FCP</a>) is similar to Largest Contentful Paint (LCP), but whilst LCP measures the largest piece of content rendered on the screen, FCP measures the first content shown. I guess it <em>is</em> in the names.</p>
<p>Your FCP could be triggered just by a small amount of text, leaving the page still looking mostly incomplete until the titles, graphics and images appear after. It doesn’t really suggest how fast the site feels, but it is helpful to understand how quickly <em>something</em> is shown, even if this is not the main content of the page.</p>
<p><strong>Time to First Byte</strong> (<a href="https://web.dev/articles/ttfb">TTFB</a>) is a very simple metric: How long does it take from the very beginning of the request to zoom off to the server and come back with data? TTFB doesn’t tell you anything about what that data is and how long it will take to fully load or appear on the screen, but it can give you some insight into how fast or far away your server is.</p>
<p><strong>Speed Index</strong> (<a href="https://developer.chrome.com/docs/lighthouse/performance/speed-index/">SI</a>) is a really handy general metric that provides a rough idea of performance, but doesn’t really tell you any specific information. It measures how quickly a page is visually complete above-the-fold (what is visible on first visit without scrolling). It’s a bit older but still handy for comparison and ongoing tracking.</p>
<p><strong>First Input Delay</strong> (<a href="https://web.dev/articles/fid">FID</a>) is similar to Interaction to Next Paint, but uses a slightly different measurement method and only considers the first input. Interaction to Next Paint is likely more useful especially once adopted by CWV but FID can give you a good impression of responsiveness.</p>
  </details>
<p></p>
<h2 data-sanitized-id="testing">Testing 101</h2>
<p>There are lots of fantastic tools and services that help you to measure and check the performance of websites. Each provides you with access to at least some of the metrics we’ve discussed above, and many will also make it easy to see where improvements can be made. These vary in depth, functionality, and difficulty so try them out and see what works best for you! Most of these tools are free, but there are some bonus paid ones.</p>
<p>One distinction to keep in mind is that not all kinds of tests are the same when it comes to web performance. There are two main types, lab tests and field tests.</p>
<ul>
<li><strong>Lab tests</strong> are run in a controlled environment, like a laboratory! They go off and do some experimentation on your website and report the result. This might be run on your computer or on a remote server. They’re very good at giving you controlled, somewhat consistent results with lots of debug information and suggestions.</li>
<li><strong>Field tests</strong> are real-world testing, including the Core Web Vitals we mentioned previously. The data they report comes from real users and is a better representation of how your website performs ‘in the wild’. They’re best for knowing how your website performs on real devices, on real networks and for your audience.</li>
</ul>
<p><a href="https://pagespeed.web.dev/">Google’s Page Speed Insights</a> is a handy tool that combines running a lab test with Core Web Vitals to gain some simple field test data. By entering a URL, you’ll get average scores across the Core Web Vitals, alongside a few other metrics from CRUX. It will also run a lab test from a Google server giving you those metrics along with some more in-depth info and suggestions. Page Speed Insights is a fantastic place to start with performance, as it makes it really easy to get the information you need and some tips on what you can improve. Don’t take the improvement times as gospel though, you will likely see some improvement but unlikely as much as it says!</p>
<p>
  </p><figure>
    <a href="https://htmhell.dev/images/advent2023/web-performance/pagespeed-insights.png">
      <picture>
        <source type="image/avif">
        <source type="image/webp">
        <img alt="screenshot of a PageSpeed Insights result for developer.mozilla.org with URL field, “Analyze” button and tabs for “Mobile” and “Desktop”. It reports “Discover what your real users are experiencing. Core Web Vitals Assessment: Passed”. It gives results for LCP, FID, CLS, FCP, INP, TTFB and some metadata. Cut off at the bottom is a heading “Diagnose performance issues”" src="https://htmhell.dev/images/advent2023/web-performance/pagespeed-insights-740.jpg" width="1000" height="586" loading="lazy">
      </picture>
    </a>
    <figcaption>Screenshot of a PageSpeed Insights result for developer.mozilla.org</figcaption>
  </figure>
<p></p>
<p><a href="https://developer.chrome.com/docs/lighthouse/overview/">Lighthouse</a> is a lab test that is built into Google Chrome and most Chromium-based browsers. It’s actually what powers the Page Speed Insights lab test and gives you the same information, but because this runs from your computer you have a little more control over the test conditions. You can access it by opening the browser developer tools with F12 and going to the “Lighthouse” or “Audit” tab. It’s a very easy and handy way to quickly check a page you’re working on.</p>
<p>
  </p><figure>
    <a href="https://htmhell.dev/images/advent2023/web-performance/lighthouse.png">
      <img alt="screenshot of Lighthouse within Google Chrome dev tools. It’s testing the HTMHell homepage and has achieved 100 scores across Performance, Accessibility, Best Practices and SEO, and PWA has confetti on it. Under Performance it gives statistics for FCP, LCP, Total Blocking Time, CLS and Speed Index" src="https://htmhell.dev/images/advent2023/web-performance/lighthouse-740.png" width="1999" height="1160" loading="lazy">
    </a>
    <figcaption>Screenshot of a Lighthouse report for htmhell.dev</figcaption>
  </figure>
<p></p>
<p>I really like <a href="https://gtmetrix.com/">GTMetrix</a> for performance tests also, and similar to Page Speed Insights and Lighthouse it’s extremely easy to use and gives you direct feedback and suggestions you can look into. It’s based on a lab test, reports all of the common metrics and has suggestions on improvements that are helpfully categorised by “Impact” which makes prioritising improvements a bit easier.</p>
<p>
  </p><figure>
    <a href="https://htmhell.dev/images/advent2023/web-performance/gtmetrix.png">
      <picture>
        <source type="image/avif">
        <source type="image/webp">
        <img alt="screenshot of GTMetrix performance report of htmhell.dev. It includes report metadata, a grading of A and performance and structure grades of 100%, the Web Vitals LCP, TBT and CLS. Below are tabs for more information including “Summary”, “Performance”, “Structure”, “Waterfall”, “Video” and “History”." src="https://htmhell.dev/images/advent2023/web-performance/gtmetrix-740.jpg" width="1000" height="586" loading="lazy">
      </picture>
    </a>
    <figcaption>Screenshot of GTMetrix performance report for htmhell.dev</figcaption>
  </figure>
<p></p>
<p><a href="https://www.webpagetest.org/">WebPageTest</a> is amazing, and is where we start to get into the more technical and powerful tools for performance testing. It runs a lab test that is extremely configurable and returns a huge amount of data that you can then analyse. Although it does give you some suggestions, its real power is exposing data about the full loading cycle to you in a way that you can really dig into. For example, did a specific third-party script delay the loading of other assets? When you need to really analyse a website in a repeatable way with as much data as possible WebPageTest is ideal.</p>
<p>
  </p><figure>
    <a href="https://htmhell.dev/images/advent2023/web-performance/webpagetest.png">
      <picture>
        <source type="image/avif">
        <source type="image/webp">
        <img alt="screenshot of webpagetest result screen. It includes a lot of metrics but is broken down into three sections, “Performance Summary”, “Page Performance Metrics” and “Visual Page Loading Process”. The Performance Summary has blocks for “Is it Quick?”, “Is it Usable?” and “Is it Resilient?”. The Page Performance Metrics lists a lot of metrics, and the Visual Page Loading Process shows page screenshots against the time they were taken." src="https://htmhell.dev/images/advent2023/web-performance/webpagetest-740.jpg" width="1000" height="586" loading="lazy">
      </picture>
    </a>
    <figcaption>Screenshot of a very small part of the WebPageTest report for htmhell.dev. There is much, much more and a lot more depth that the summary you see here</figcaption>
  </figure>
<p></p>
<p>In addition to Lighthouse, Chromium-based browsers have some very handy performance tools built into the development tools. The <strong>Network pane</strong> is handy for seeing the full list of requests made by the browser, where they came from and how long they took. This is ideal for checking request order, caching behaviour, and what third-parties are being connected to. The <strong>Performance pane</strong> gives you even more information about how the browser is performing, including JavaScript call stacks, frame counts, tasks in the event loop, and more. These are pretty advanced tools but very powerful.</p>
<p>
  </p><figure>
    <a href="https://htmhell.dev/images/advent2023/web-performance/chrome-performance.png">
      <picture>
        <source type="image/avif">
        <img alt="screenshot of Performance pane within Chrome Devtools. There’s a lot going on including a filmstrip of the site loading, Network Graph and collapsed sections for Timings, Main, GPU, and more. At the bottom there is a Summary and tabs for Bottom-Up, Call Tree and Event Log" src="https://htmhell.dev/images/advent2023/web-performance/chrome-performance-740.jpg" width="1000" height="586" loading="lazy">
      </picture>
    </a>
    <figcaption>Screenshot of the Performance tab within the Chrome developer tools. The first things you would look at here are Network, Timings, Frames and Main</figcaption>
  </figure>
<p></p>
<p><strong>Firefox and Safari</strong> also have handy performance tools built-in. Safari has the Timelines tab which records a page load and provides screenshots and a huge amount of detail on Network Requests, Layout &amp; Rendering, JavaScript &amp; Events, and CPU usage. Firefox’s Network tab works similarly to Chrome’s with the addition of a quick “Performance Analysis” which reports details about content types, weights, and caching.</p>
<p>We’ve discussed the Chrome User Experience Report a few times already, and a couple of methods of getting that data. One way you can access that data, particularly when looking at how average measurements have changed over time, is by using the <strong>CRUX History API</strong>. There are a few tools for accessing this, but my favourite is through <a href="https://colab.research.google.com/github/GoogleChrome/CrUX/blob/main/colab/crux-history-api.ipynb">the CRUX History API notebook on Google Colab</a>. By inserting a URL and a free CRUX API key you’ll be able to see graphs of how CRUX metrics have changed over time.</p>
<p>Those are the main free tools which I use regularly for testing, but there are a few others that may cost money or are a bit more specific:</p>
<ul>
<li><a href="https://calibreapp.com/">Calibre</a> — automated lab testing and monitoring</li>
<li><a href="https://www.speedcurve.com/">Speedcurve</a> — automated lab and field testing, and monitoring</li>
<li><a href="https://github.com/zachleat/speedlify">Speedlify</a> — open-source tester and aggregator of web performance and accessibility</li>
<li><a href="https://treo.sh/">Treo</a> — automated lab testing and core web vitals analysis</li>
<li>Open-source tools based on Lighthouse — there are a ton of open-source tools that use lighthouse for whole-site crawling and testing and more</li>
</ul>
<h2 data-sanitized-id="tips">Ten Wild Web Performance Tips! You’ll be saving number 5 for later!</h2>
<p>Okay, now it’s time for the section we’ve all been waiting for, unless you skipped here from the links in the intro. If you did, you missed some real great stuff up there and at least three jokes that I should have never committed to writing.</p>
<p>On these specific tips, they are some of the most common issues and fixes for websites I encounter. Not every site has all of them, but generally at least a handful. Going into the exact code and details for each will put everyone to sleep, but they’ll give you somewhere to start looking!</p>
<h3 data-sanitized-id="1-test!-test!-test!">1. Test! Test! Test!</h3>
<p>Tools like the ones above can make it easy to see a simple summary of web performance and give you tips on what to focus on. Run some lab tests after major changes to confirm you’ve actually improved performance, or to check you haven’t caused a regression.</p>
<p>Also consider setting up automated performance monitoring to monitor performance over time, alert you to regressions, and to flag issues on specific pages before lots of visitors are affected.</p>
<h3 data-sanitized-id="2-optimise-images-optimise-them-again-and-then-once-more-for-good-luck">2. Optimise images, optimise them again, and then once more for good luck</h3>
<p>Unless you already have perfectly optimised images, they are always the first place to look for performance improvements. In most websites the Largest Contentful Paint element is an image, and generally a huge amount of the total ‘weight’ of pages are images.</p>
<p>There are a few different things you can and should do when optimising images:</p>
<ul>
<li>Compress images to reduce file size at the cost of quality. If a 85% quality image at 100kB looks identical to a 100% one at 2MB it’s an easy choice</li>
<li>Convert to more recent image formats like WebP, AVIF or JpegXL. These formats may make certain images a lot smaller than their JPEG and PNG counterparts</li>
<li>Resize images to the exact size you need them, and use <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">responsive images</a> with <code>srcset</code> to offer smaller sizes. This way a browser doesn’t download a huge image it doesn’t need</li>
</ul>
<p>Manual processing tools like <a href="https://squoosh.app/">squoosh.app</a> make it easy to convert, resize, and optimise images by hand. A CMS may also have partial or full automatic optimisation, or you could consider an image CDN like <a href="https://cloudinary.com/">Cloudinary</a>, <a href="https://imgix.com/">Imgix</a> or <a href="https://www.cloudimage.io/">CloudImage</a> to help optimise and resize images on request.</p>
<p>You can also use <code>loading="lazy"</code> on your image elements to wait until they’re in view and needed before downloading them, and <a href="https://web.dev/articles/fetch-priority">Priority Hints</a> with <code>fetchpriority="high"</code> to ask the browser to prioritise downloading your LCP image sooner.</p>
<h3 data-sanitized-id="3-as-little-javascript-as-possible">3. As little JavaScript as possible</h3>
<p>JavaScript is the most expensive resource in terms of performance on the web, and is a really common bottleneck when used too much.</p>
<p>Where you can implement something in HTML or CSS then do, and make sure your pages can start rendering without JavaScript.</p>
<p>Avoid large JS libraries where you can and instead use built-in JavaScript APIs, progressive enhancement, or smaller and more performant libraries.</p>
<h3 data-sanitized-id="4-watch-out-for-third-parties">4. Watch out for third-parties</h3>
<p>Third-party code, tag managers, analytics tools, embeds, and more can have a huge impact on performance. Where it’s possible, consider removing these, or at least take measurements to make sure everyone is aware of their impact!</p>
<p>You’ll be amazed at the number of marketing departments that ask you to “fix their site speed” despite having seven tag managers enabled.</p>
<h3 data-sanitized-id="5-cache-me-if-you-can">5. Cache me if you can</h3>
<p>If someone has visited your site and their browser has downloaded all the resources once before, should they really need to do so again?</p>
<p>You can <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching">use caching headers</a> to make repeat and subsequent visits a huge amount faster by reusing static assets like CSS and JavaScript instead of having to re-download them frequently.</p>
<h3 data-sanitized-id="6-be-bold-optimise-your-fonts">6. Be bold, optimise your fonts</h3>
<p>One of the biggest culprits for Cumulative Layout Shift issues is due to web fonts. With a poorly optimised web font you may find your website loading almost entirely with a fallback font or invisible text, and then the entire page shifting as the font kicks in at a different size.</p>
<p>In an ideal performance world you could consider using a system font to avoid loading web fonts at all. In most cases though we can keep the web font but optimise it to make it load faster and more smoothly!</p>
<ul>
<li>Serve the font from your origin/domain instead of a third-party</li>
<li>Limit the number of font files you need to load by reducing the number of weights you use. Do you really need a regular, medium and a semibold?</li>
<li>Use <code>font-display:swap;</code> within <code>@font-face</code> to smooth the switch from fallback to custom fonts</li>
<li>Subset fonts to just the characters you need with a tool like <a href="https://github.com/zachleat/glyphhanger">glyphhanger</a>. If you have a website in only ASCII characters you can reduce your font sizes by up to 50%!</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide">Variable Fonts</a> combine multiple font weights and styles into a single font file. Whilst it may be larger than a single non-variable font, it’ll generally be smaller than 3 traditional fonts. Go variable it if you can!</li>
<li>Preload the most important font or two using <code>&lt;link rel="preload" as="font"&gt;</code></li>
</ul>
<p>Depending on the licence of your font you may not be able to do all of the above — particularly subsetting or loading from your server — so make sure you double-check the font licence first. As Google Fonts are open-source you can do all of the above!</p>
<p><a href="https://www.zachleat.com/web/comprehensive-webfonts/">Zach Leatherman</a> and <a href="https://csswizardry.com/2020/05/the-fastest-google-fonts/">Harry Roberts</a> have some fantastic further reading for font optimisation.</p>
<h3 data-sanitized-id="7-server-side-speed-shenanigans">7. Server side speed shenanigans</h3>
<p>This is one for the full-stackers, back-enders, all-rounders, and devops!</p>
<p>If your website relies on a single, slow server then you may have bottlenecked your site performance on your server speed. If it takes 2 seconds before the browser receives the HTML to render then no matter how much you do on the front-end that’s going to be your limit. You can get an idea of this by looking at the Time to First Byte on a fast connection close to the server. It depends on the systems and work being done but you want to be as close to 0ms as possible, definitely under 500ms.</p>
<p>You can improve this by optimising your servers and code, using a CDN like <a href="https://bunny.net/pricing/">Bunny CDN</a> or <a href="https://www.fastly.com/">Fastly</a> to cache HTML that’s the same for every user, or by speaking to your web host.</p>
<h3 data-sanitized-id="8-stop-sharding-or-too-many-origins">8. Stop sharding, or too many origins</h3>
<p>Back in the day it was good advice to split up your assets across multiple origins/domains to deal with connection limits in HTTP 1, and to use public CDNs for shared caching. That hasn’t been good advice for a long time now since HTTP/2. It’s time to get everything on one origin!</p>
<p>Put as many assets on your main domain as possible. HTTP/2 or 3 can easily handle those resources and it avoids extra connections to new domains which takes valuable time.</p>
<p>Browsers used to allow websites to re-use a cached resource with the same URL across different domains. This provided a way to track users however so <a href="https://www.stefanjudis.com/notes/say-goodbye-to-resource-caching-across-sites-and-domains/">all browsers now separate the cache of each origin</a>. Using a public CDN no longer gives any benefit to resource caching.</p>
<h3 data-sanitized-id="9-split-that-code">9. Split that code</h3>
<p>If you have some CSS or JavaScript that runs on only a single page, consider loading that separately only when needed, rather than across the entire website. It will improve the speed of all other pages, and avoid the extra cost of downloading something that a user might never use!</p>
<h3 data-sanitized-id="10-consider-preloads-but-here-be-dragons">10. Consider preloads, but here be dragons</h3>
<p>Preloads are a way of having more control over how the browser loads resources. You can use them to ask for certain assets to be downloaded in advance of when they’re needed, or to make the browser prioritise one asset over another.</p>
<p>Be careful with them however, preloading one asset will always delay the loading of something else so it can be very easy to accidentally make things worse!</p>
<p>Preloads are particularly handy for font files, where the browser won’t start downloading them until the CSS has downloaded and rendering has begun. It can also be used for images or other assets that you’re finding are discovered too late.</p>
<p><a href="https://web.dev/articles/preload-critical-assets">Web.dev has an article about preloading critical assets</a>.</p>
<h2 data-sanitized-id="what-next">What next?</h2>
<p>Now my friends, go forth and make fast websites! I hope that you’ve gained more knowledge, confidence, understanding, and resources to test, diagnose and make websites perform better.</p>
<p>This is just the beginning, and there are loads of fantastic resources and people to follow to further your journey to Practised Performance Prophet!</p>
<h3 data-sanitized-id="courses">Courses</h3>
<ul>
<li><a href="https://www.webpagetest.org/learn/lightning-fast-web-performance/">Lightning Fast Web Performance</a> by Scott Jehl</li>
<li><a href="https://csswizardry.gumroad.com/l/eihdtmcwf">Everything I Have Done to Make CSS Wizardry Fast</a> by Harry Roberts</li>
</ul>
<h3 data-sanitized-id="books">Books</h3>
<ul>
<li><a href="https://abookapart.com/products/responsible-javascript">Responsible JavaScript</a> by Jeremy Wagner — Book</li>
<li><a href="https://www.smashingmagazine.com/2021/04/image-optimization-pre-release/">Image Optimization</a> by Addy Osmani — Book</li>
</ul>
<h3 data-sanitized-id="blogs">Blogs</h3>
<ul>
<li><a href="https://web.dev/explore/metrics">Google's web.dev</a></li>
<li><a href="https://csswizardry.com/archive/">CSS Wizardry by Harry Roberts</a></li>
<li><a href="https://www.zachleat.com/">Zach Leatherman</a></li>
<li><a href="https://jakearchibald.com/">Jake Archibald</a></li>
<li><a href="https://sia.codes/">Sia Karamalegos</a></li>
<li><a href="https://calibreapp.com/blog">Calibre</a></li>
<li><a href="https://www.speedcurve.com/blog/">SpeedCurve</a></li>
<li><a href="https://cloudfour.com/">Cloudfour</a></li>
<li><a href="https://calendar.perfplanet.com/">Web Performance Calendar</a></li>
<li><a href="https://www.debugbear.com/blog">DebugBear</a></li>
</ul>


