<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Nikki's Notes]]></title><description><![CDATA[I'm a developer determined to make the web more usable and joyful for everyone. I hope you enjoy my articles and welcome you to comment and connect!]]></description><link>https://blog.nikkibright.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 08 May 2026 14:58:45 GMT</lastBuildDate><atom:link href="https://blog.nikkibright.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Create Accessible Footnotes]]></title><description><![CDATA[Introduction
Footnotes have a long and storied history dating back to the Middle Ages. For hundreds of years, they have been a powerful tool for providing information that supplements and supports the main text. By providing an easy way to access sou...]]></description><link>https://blog.nikkibright.com/how-to-create-accessible-footnotes</link><guid isPermaLink="true">https://blog.nikkibright.com/how-to-create-accessible-footnotes</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Accessibility]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[content]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Nikki Bright]]></dc:creator><pubDate>Tue, 20 Jun 2023 16:49:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/iE9Iq-ys8Bo/upload/4d2aa2bee4730f03bbd0bd5e863b4ede.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Footnotes have a long and storied history dating back to the Middle Ages. For hundreds of years, they have been a powerful tool for providing information that supplements and supports the main text. By providing an easy way to access source references or additional information, footnotes have withstood the test of time. As a modern example, my personal favorite use of footnotes can be found in the fantasy novels of the late Terry Pratchett, where they are used to provide world-building and character insights in the voice of a hilarious outside narrator.</p>
<p>Just as footnotes can be an incredible tool on paper, they can also do great work on a web page. Clicking on an inline footnote reference can lead readers directly to the supplemental information in the footnote. Once they're finished, it can also take them back to their previous position on the page.</p>
<p>This is easy enough to set up to work with a mouse, but we also want to make sure our lovely keyboard and assistive technology users can reap the benefits of footnotes just as easily.</p>
<h2 id="heading-accessibility-guidelines">Accessibility Guidelines</h2>
<p>There are some key factors of accessibility to keep in mind when coding up some footnotes:</p>
<ol>
<li><p><strong>Semantic structure</strong>: Using semantic structure provides meaningful and properly organized content that can be interpreted accurately by assistive technologies, allowing users with disabilities to navigate, comprehend, and interact with web pages effectively.</p>
</li>
<li><p><strong>Keyboard accessibility</strong>: Making websites keyboard accessible ensures that folks who can't use a mouse or rely on alternative input methods can still navigate and interact with them, allowing for inclusive and barrier-free access.</p>
</li>
<li><p><strong>Support for assistive technologies</strong>: Supporting assistive technologies like screen readers enables people with visual impairments or reading difficulties to access and comprehend web content by converting text and other visual elements into audible or tactile representations.</p>
</li>
</ol>
<h2 id="heading-implementation">Implementation</h2>
<p>Let's take this one step at a time. Since we're focused on accessibility here, we'll work through the accessibility factors mentioned above.</p>
<h3 id="heading-semantic-structure-andamp-keyboard-accessibility"><strong>Semantic structure &amp; keyboard accessibility</strong></h3>
<p>Oftentimes, keyboard accessibility can be tackled entirely by using proper semantic HTML. This is one of those cases.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
 This is some example text supported by a footnote
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"footnote-1-link"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#footnote-1"</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">sup</span>&gt;</span>[1]<span class="hljs-tag">&lt;/<span class="hljs-name">sup</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
 .
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>


<span class="hljs-tag">&lt;<span class="hljs-name">ol</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"footnote-1"</span>&gt;</span>
  Footnotes provide supportive information.
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#footnote-1-link"</span>&gt;</span>↩<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
</code></pre>
<ol>
<li><p>The link to the footnote (ex. "[1]") is contained by a <code>&lt;a&gt;</code> tag.</p>
</li>
<li><p>The <code>&lt;a&gt;</code> tag has a unique <code>id</code> attribute and an <code>href</code> attribute set to the <code>id</code> of the footnote it links to.</p>
</li>
<li><p>The <code>&lt;a&gt;</code> tag wraps a <code>&lt;sup&gt;</code> tag. You're probably wondering "What's <code>&lt;sup&gt;</code>, dog?" "Sup" is short for "superscript," meaning the text is small and raised to the top of the line height. This is the recommended presentation for footnote links, ideally consistent across the web.</p>
</li>
<li><p>The footnotes at the bottom of the page are contained in an <code>&lt;ol&gt;</code> tag since the order of the footnotes matters.</p>
</li>
<li><p>The <code>&lt;li&gt;</code> tag containing the footnote has a unique <code>id</code> attribute, matching the <code>href</code> attribute on the footnote link.</p>
</li>
<li><p>The footnote <code>&lt;li&gt;</code> tag contains a <code>&lt;a&gt;</code> tag that links back to the corresponding footnote link. The <code>href</code> attribute matches the <code>id</code> attribute of the footnote link. If multiple footnote links lead to the same footnote (like if one reference is used in multiple places on a page) JavaScript will be required to dynamically change the <code>href</code> attribute of the link in the footnote to take users back to where they came from.</p>
</li>
</ol>
<h3 id="heading-support-for-assistive-technologies">Support for assistive technologies</h3>
<p>Using semantic HTML already takes us pretty far when it comes to supporting assistive technologies like screen readers and refreshable braille displays. But we can do better by increasing the clarity of our footnote links and "back to content" links. The code example below is mostly the same, but there are a few important changes.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
 This is some example text supported by a footnote
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"footnote-1-link"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#footnote-1"</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">sup</span>&gt;</span>[1] <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sr-only"</span>&gt;</span>Footnote details<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">sup</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
 .
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>


<span class="hljs-tag">&lt;<span class="hljs-name">ol</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"footnote-1"</span>&gt;</span>
  Footnotes provide supportive information.
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#footnote-1-link"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Back to content"</span>&gt;</span>↩<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
</code></pre>
<ol>
<li><p>Add a <code>&lt;span&gt;</code> tag inside of the <code>&lt;sup&gt;</code> tag with a description of where the footnote link leads, such as "footnote details." This way, screen readers will read out more than just "one" for the link. Use CSS to hide this text visually but keep it on the page for screen readers and other assistive technologies.</p>
</li>
<li><p>In the above example, the "back to content" link in the footnote is displayed as an icon (↩). This isn't readable by screen readers and other assistive technologies, so we can give it a text alternative with the <code>aria-label</code> attribute.</p>
</li>
</ol>
<h2 id="heading-outroduction">Outroduction</h2>
<p>Now we know how to make accessible footnotes! To summarize:</p>
<ol>
<li><p>Use proper semantic HTML with tags like <code>&lt;a&gt;</code> and <code>&lt;sup&gt;</code>.</p>
</li>
<li><p>Use matching <code>href</code> and <code>id</code> attributes on footnote links and "back to content" links.</p>
</li>
<li><p>Improve the experience for folks using assistive technologies like screen readers by utilizing "screen reader only" CSS and the <code>aria-label</code> attribute.</p>
</li>
</ol>
<p>For more information about accessible footnotes, check out the <a target="_blank" href="https://hyah-ui.vercel.app/accessibility/text.html#footnotes">footnotes section of Hyah UI</a>.</p>
<p>Have a question about how to implement any of the above steps? Or maybe you've caught something you think is missing or incorrect? If any of that sounds like you, comment down below so we can chat about it!</p>
]]></content:encoded></item><item><title><![CDATA[How to Make a "Skip Navigation" Link]]></title><description><![CDATA[What's a "Skip Navigation" link and why do I need one?
If you're in the majority of users (statistically speaking, you probably are), you can look at a page as soon as it loads and start viewing the main content right away. By automatically ignoring ...]]></description><link>https://blog.nikkibright.com/how-to-make-a-skip-navigation-link</link><guid isPermaLink="true">https://blog.nikkibright.com/how-to-make-a-skip-navigation-link</guid><category><![CDATA[Accessibility]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Web Accessibility]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Nikki Bright]]></dc:creator><pubDate>Tue, 06 Jun 2023 15:15:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/7vSlK_9gHWA/upload/65563f2fafae3f9540af87e619e48228.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-whats-a-skip-navigation-link-and-why-do-i-need-one">What's a "Skip Navigation" link and why do I need one?</h2>
<p>If you're in the majority of users (statistically speaking, you probably are), you can look at a page as soon as it loads and start viewing the main content right away. By automatically ignoring the elements that are present on all pages of a site (header navigation, left-side ads, etc), you can dive right into what brought you there in the first place. Grab hold of your mouse and click right on the button or link you want, regardless of where it is on the page.</p>
<p>Unfortunately, many users aren't able to do this. Some examples are:</p>
<ul>
<li><p>Users with low vision, blindness, or a cognitive disability may use a <strong>screen reader</strong> to navigate the web, which reads out elements on web pages one at a time.</p>
</li>
<li><p>Users who have difficulty making the precise movements necessary to use a mouse may primarily or exclusively use a <strong>keyboard</strong> to interact with the web, using the Tab key to move through each interactive element in order. Many power users also use keyboards to navigate web pages or web applications quickly.</p>
</li>
</ul>
<p>Without the ability to jump to the part of the page they wish to engage with, they have to start at the top and work their way down every page. That's so dang tedious!</p>
<p>This issue is the reason behind <a target="_blank" href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-skip.html">WCAG Success Criterion 2.4.1: Bypass Blocks</a>, requiring that "a mechanism is available to bypass blocks of content that are repeated on multiple Web pages."</p>
<p>Luckily for us, there is a simple way to account for this: the "Skip Navigation" or "Skip to Main Content" link.</p>
<p>A "Skip Navigation" link sits at the top of a web page as the very first interactive element. Before proceeding into the page's header elements and nested navigation menus, they have the option to skip past that nonsense and get to the juicy main content.</p>
<h2 id="heading-how-do-i-make-a-skip-navigation-link">How do I make a "Skip Navigation" link?</h2>
<p>The steps for creating great "Skip Navigation" links are straightforward:</p>
<ol>
<li><p>Give the main content an <code>id</code>.</p>
</li>
<li><p>Add a link to the top of the page that targets the main content.</p>
</li>
<li><p>Use JavaScript to change focus (if necessary).</p>
</li>
<li><p>Hide the link with CSS.</p>
</li>
</ol>
<h3 id="heading-give-the-main-content-an-id">Give the main content an <code>id</code></h3>
<p>For the "Skip Navigation" link to know where the main content is on the page, we need to give it a unique identifier that the link can target. This is done by slapping an <code>id</code> property on the wrapping element.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"maincontent"</span>&gt;</span>
...
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<p>It's recommended that your page uses a <code>&lt;main&gt;</code> element to wrap the main content of your page so that assistive technologies like screen readers can identify it. That's a topic for a different article, so we'll leave it at that for now.</p>
<h3 id="heading-add-a-link-to-the-top-of-the-page-that-targets-the-main-content">Add a link to the top of the page that targets the main content</h3>
<p>Now that we have a way for our new link to find the main content, we need to add said link to the page. This will be an <code>&lt;a&gt;</code> element with an <code>href</code> property set to the <code>id</code> of the main content (see the previous step), preceded by a <code>#</code>.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#maincontent"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"skiplink"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"skiplink"</span> &gt;</span>Skip to Main Content<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</code></pre>
<p>Activating that link will take the user to the main content, skipping past that pesky navigation menu so they can slurp up that sweet sweet main content. Yum.</p>
<p><strong>Note</strong>: The text in the Skip Navigation link must be straightforward to understand quickly since this is all about getting users where they need to go faster and more conveniently. With that in mind, it is recommended that the text used for the link is either "Skip Navigation" or, my personal preference, "Skip to Main Content."</p>
<h3 id="heading-use-javascript-to-change-focus-if-necessary">Use JavaScript to change focus (if necessary)</h3>
<p>Activating our cute little "Skip Navigation" link takes us to the correct spot on the page now, but sometimes that isn't quite enough to get the job done. Depending on the browser and the setup of the page, the focus may not have moved to the main content like we want. Using JavaScript we can make sure that activating the link also moves focus past the unwanted elements.</p>
<p>First, we need to make sure the main content wrapper is focusable by giving it a <code>tabindex</code> property. We don't want it to be added to the page's tab sequence though. If we set the <code>tabindex</code> to <code>-1</code>, the element becomes focusable but doesn't exist in the tab sequence. Perfection.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"maincontent"</span> <span class="hljs-attr">tabindex</span>=<span class="hljs-string">"-1"</span>&gt;</span>
...
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<p>Next, we just add an event listener to the "Skip Navigation" link so the focus is correctly moved whenever it is activated. We can use the <code>focus()</code> function on the DOM <code>Node</code> class to accomplish this.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> skipNavLink = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#skiplink"</span>);

skipNavLink.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> mainContent = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#maincontent"</span>);
  mainContent.focus();
});
</code></pre>
<p>With that, clicking or pressing Enter on the "Skip Navigation" link will jump the user to the main content and ensure that the keyboard focus has skipped all prior elements in the tab sequence!</p>
<h3 id="heading-hide-the-link-with-css">Hide the link with CSS</h3>
<p>Whoa whoa whoa whoa <em>whoa</em>. "Hide the link?" Didn't we just go through the trouble of making it? Why would we hide it?!</p>
<p>Well, hear me out.</p>
<p>Yes, we have this incredibly useful link that improves the experience of lots of our users. But what about the majority of users who don't need it? There's no reason to clutter up the UI with links most people don't need. Thus, we hide it!</p>
<p>Using CSS, we can hide the "Skip Navigation" link by default, and only display it when it's needed. Translation: if the link isn't focused, we don't need to see it.</p>
<p>There is a plethora of ways to accomplish making the "Skip Navigation" link only visible when focused. Here is one:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.skiplink</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">left</span>: -<span class="hljs-number">9999px</span>;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1000</span>;
}

<span class="hljs-selector-class">.skiplink</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">left</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(-<span class="hljs-number">50%</span>);
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
}
</code></pre>
<p>As I said, this is just one way of conditionally hiding the link. The important part is that it's completely hidden by default, and is visible when focused.</p>
<p>Also, while you're in the CSS, you might as well style up your link to look just as rad as the rest of your site! <strong>The users who need this link must have the same quality experience as other users</strong>. Everyone deserves to see your killer designs!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So there you have it. Now you have a dope as heck "Skip Navigation" link that improves the experience of any user of your site that doesn't use a mouse.</p>
<p>Have a question about how to implement any of the above steps? Or maybe you've caught something you think is missing or incorrect? If any of that sounds like you, comment down below so we can chat about it!</p>
]]></content:encoded></item><item><title><![CDATA[How to Improve Your Image Performance]]></title><description><![CDATA[The Exposition (What?)
Boy, do I love websites with great imagery. Pictures of landscapes. Pictures of cats. Pictures of pictures. Pictures of graphs. Putting images on a site can make it miles more interesting.
That's not the only thing great about ...]]></description><link>https://blog.nikkibright.com/how-to-improve-your-image-performance</link><guid isPermaLink="true">https://blog.nikkibright.com/how-to-improve-your-image-performance</guid><category><![CDATA[performance]]></category><category><![CDATA[images]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Nikki Bright]]></dc:creator><pubDate>Thu, 09 Feb 2023 20:27:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/mgaS4FlsYxQ/upload/3a186567aa3e5720d6a5dec086567388.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-exposition-what">The Exposition (What?)</h2>
<p>Boy, do I love websites with great imagery. Pictures of landscapes. Pictures of cats. Pictures of pictures. Pictures of graphs. Putting images on a site can make it miles more interesting.</p>
<p>That's not the only thing great about using images, though. People with low-vision or a cognitive disability may find it difficult to read or process text on its own. Supplementing text content with a picture or graphic is great for helping all users experience what your site has to offer. Plus, images let me insert pictures of my dog Mayari anywhere I want. Like so:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675906447604/3e062abb-458e-4079-a55c-1b532714bc4b.jpeg" alt="The author's beautiful red Irish-doodle dog, Mayari." class="image--center mx-auto" /></p>
<p>So, yeah, images are pretty dope. But with great imagery, as they say, comes great responsibility.</p>
<h2 id="heading-the-rising-action-why">The Rising Action (Why?)</h2>
<p>Images can cause pretty unfortunate performance issues when used without care (anyone who knows their way around <a target="_blank" href="https://developer.chrome.com/docs/lighthouse/overview/">Google Lighthouse</a> knows what I'm talking about). Most notably, images can have a significant impact on a page's Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS).</p>
<p>When a webpage is loading, a single prominent element can cause users to believe the entire page is slow even if every other element is ready to rock. It doesn't matter if 90% of your site is lightning-fast if your hero photo takes two seconds to appear. It's not until they see the picture that users believe the page is just about ready to be useful. The measurement of how bad this is is called the Largest Contentful Paint or LCP. LCP is the amount of time that passes before the largest area of the screen is rendered. Since image elements are often used at the top of pages to quickly grab users' interest, not optimizing them for performance can cause users to believe the site is too slow and leave.</p>
<p>Have you ever tried to click on a link right after a page loads only for the button to jump down causing you to click on an ad for travel insurance instead? You just experienced a high Cumulative Layout Shift or CLS. CLS is the quantification of how often users experience unexpected shifts on the page. Poorly handled images can cause these infuriating gotchas by spontaneously appearing and taking up space.</p>
<p>Having high LCP and CLS scores can have a significant negative impact on user stickiness/session duration. Luckily, there are some things we can do to ensure our users don't have to wait for that sick banner photo of a dog catching a frisbee.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675719495171/a19f8b5b-ba4a-4dd4-9dc0-71fc41e88117.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-climax-how">The Climax (How?)</h2>
<h3 id="heading-use-the-best-format">Use the best format</h3>
<p>I'm gonna make this easy for all of us. If your image names don't already end in <code>.webp</code>, then fixing that is the first step. WebP offers all the benefits of the classic formats we know and love (looking at you, PNG and JPEG), but they're much better at lossy and lossless compression. What that means for you is that they're generally harder, better, faster, and smaller.</p>
<p>There are a ton of super easy ways to convert your existing images to WebP. Google "convert image to WebP" and take your pick. If you have an absolute ton to convert or want more granular control though, you can do it scriptomatically. I made my tool for converting and optimizing images (<a target="_blank" href="https://github.com/nikkistorme/image-optimizer">check it out</a>) in Node using the npm package <a target="_blank" href="https://sharp.pixelplumbing.com/">sharp</a>.</p>
<h3 id="heading-compress">Compress</h3>
<p>Believe it or not, images on a web page can be <em>too</em> good. If an image is so high-resolution that you can make out each grain of sand in the sandcastle you built with your niece, it's way too high. The bad news: you need to compress that image. The good news: no one will notice if you do it right.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675455198319/22a1b4d8-da19-41a8-aa7d-8b55b448a4dd.jpeg" alt="An expertly constructed sandcastle in front of a picturesque body of water." class="image--center mx-auto" /></p>
<p>Compressing your images can be as easy or as hard as you want. If you have a small number of images to compress, find a compressor online and drop them in. Typically you'll need to designate what level of quality you want. In this case, I recommend some good old-fashioned trial and error to find how much you can compress your image without a noticeable loss in quality. You can also do it scriptomatically, like I did <a target="_blank" href="https://github.com/nikkistorme/image-optimizer">here</a>.</p>
<p>If you're set on going deep into image compression, you'll find there are multiple different algorithms and philosophies on how best to do it. Delving into that is more than I've ever needed, but I'm sure some of you have or will get down and dirty with it.</p>
<h3 id="heading-use-responsive-image-tags">Use responsive image tags</h3>
<p>Go ahead and let those pants out an extra inch 'cause this part is a bit beefy.</p>
<p>It truly, <em>truly</em> is not necessary for a mobile user to see the same size picture as a desktop user. Mobile devices typically take much longer to load images, and I promise that no one is going to zoom in close so they can see how many freckles you have. For this reason, we might as well have users on mobile devices see a smaller version of the image so they don't have to wait for the bulky desktop version. Images that behave differently based on screen size are called "responsive images."</p>
<p>HTML offers everything we need to serve responsive images, with no fancy-pants JavaScript required. There are a few ways to do it though, and it's important to know when to use each method.</p>
<ol>
<li><p><strong>When the image is small enough to not need responsiveness</strong></p>
<ul>
<li><p>Use this for stuff like logos or icons that are always the same size. This method is what most of us are used to. Nice and safe.</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/assets/sandcastles_in_the_sand.webp"</span>
       <span class="hljs-attr">alt</span>=<span class="hljs-string">"An impressive sandcastle the author built with his niece. Features battlements and a moat."</span>
       <span class="hljs-attr">width</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"150"</span>
  &gt;</span>
</code></pre>
</li>
</ul>
</li>
<li><p><strong>When the image needs responsiveness, but always looks the same</strong></p>
<ul>
<li><p>Make use of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset"><code>srcset</code></a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes"><code>sizes</code></a> attributes.</p>
</li>
<li><p><code>src</code> points to the version of the image to use if the browser doesn't support <code>srcset</code> (pretty much just Internet Explorer).</p>
</li>
<li><p><code>srcset</code> lists all the images the browser can choose from, paired with each image’s width.</p>
</li>
<li><p><code>sizes</code> holds the rules for when to display the images listen in <code>srcset</code>. A media condition followed by a size that corresponds to one of the images in <code>srcset</code>.</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"puppy-doggy-900w.webp"</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"An overwhelmingly cute Irish setter puppy with red hair."</span>
      <span class="hljs-attr">srcset</span>=<span class="hljs-string">"puppy-doggy-600w.webp 600w,
              puppy-doggy-900w.webp 900w,
              puppy-doggy-1200w.webp 1200w,
              puppy-doggy-1200w.webp 1200w, 2400w"</span>
      <span class="hljs-attr">sizes</span>=<span class="hljs-string">"(max-width: 600px) 600px,
             (max-width: 900px) 900px,
             (max-width: 1200px) 1200px,
             2400px"</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"900"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"900"</span>
  /&gt;</span>
</code></pre>
</li>
</ul>
</li>
<li><p><strong>When different images need to be shown based on screen size</strong></p>
<ul>
<li><p>Use the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"><code>&lt;picture&gt;</code></a>, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"><code>&lt;source&gt;</code></a>, and <code>&lt;img&gt;</code> tags, along with the <code>media</code> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset"><code>srcset</code></a> attributes.</p>
</li>
<li><p>The browser picks the image URL to use from <code>srcset</code>, based on the first <code>&lt;source&gt;</code> with a <code>media</code> attribute value that matches the user's viewport width.</p>
</li>
<li><p><code>&lt;img&gt;</code> will be used as a fallback if <code>&lt;source&gt;</code> isn’t supported.</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">picture</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(min-width: 45em)"</span>
              <span class="hljs-attr">srcset</span>=<span class="hljs-string">"big-wolf.jpg"</span>
              <span class="hljs-attr">width</span>=<span class="hljs-string">"600"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"600"</span>
      &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(min-width: 32em)"</span>
              <span class="hljs-attr">srcset</span>=<span class="hljs-string">"medium-wolf.jpg"</span>
              <span class="hljs-attr">width</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"400"</span>
      &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"small-wolf.jpg"</span>
          <span class="hljs-attr">alt</span>=<span class="hljs-string">"A wolf scampers menacingly through the snow."</span>
          <span class="hljs-attr">width</span>=<span class="hljs-string">"300"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"300"</span>
      &gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">picture</span>&gt;</span>
</code></pre>
</li>
</ul>
</li>
</ol>
<h3 id="heading-lazy-loading">Lazy loading</h3>
<p>Ok, talking about responsive imagery was a whole meal. Luckily, this next part is a snack.</p>
<p>When you visit a webpage, the browser asks and (hopefully) receives instructions about what to put on the page and how to do it. Then, it asks the server if it would please send over those images mentioned in the instructions it kindly shared. Instead of waiting for an immediate response (sometimes servers, like people, take a bit to get back to you), the browser presses on. Once it's ready to start painting on the content, it starts from the top and moves its way down to the bottom.</p>
<p>As we've discussed already, sometimes images take a sec (hopefully less) to render in. If an image is so big (or a server is so slow) that it still hasn't arrived by the time the browser wants to paint it in, the entire show stops and waits for it. Then users and Google Lighthouse all get upset.</p>
<p>But, because the page renders top to bottom and screens are only so big, we don't need to make users wait for <em>all</em> of the images on a page. If there's an image of a logo in the footer, we can tell the browser to not hold everything up. I know, that logo designed by your sister-in-law's dog groomer looks <em>so</em> professional and perfectly represents your brand. Everyone will appreciate it more if they don't have to wait for it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675455269346/51de78a5-15aa-4aa9-bd3b-ea4f8369e606.jpeg" alt class="image--center mx-auto" /></p>
<p>If an image doesn't need to be displayed right away, the solution is easy. Slap <code>loading="lazy"</code> on that bad boy. This tells the browser not to wait, to keep calm, and carry on serving up the page.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"vape-store-logo.jpg"</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">“lazy”</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Victors Vapes logo."</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"150"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"100"</span>&gt;</span>
</code></pre>
<h3 id="heading-give-all-images-width-and-height">Give all images <code>width</code> and <code>height</code></h3>
<p>Now, back to those pages that jump up and down as they load. We chatted for a bit about Cumulative Layout Shift (CLS) already. CLS from images can easily be addressed by slapping <code>width</code> and <code>height</code> attributes on your images. This will tell the browser how much space to put aside so that the image has room to pop in when it's ready.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span>
    <span class="hljs-attr">src</span>=<span class="hljs-string">"sandcastles-in-the-sand.webp"</span>
    <span class="hljs-attr">alt</span>=<span class="hljs-string">"An impressive sandcastle the author built with his niece. Features battlements and a moat."</span>
    <span class="hljs-attr">width</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"150"</span>
&gt;</span>
</code></pre>
<h2 id="heading-the-falling-action-summary">The Falling Action (Summary)</h2>
<p>As we've discussed, images are magnificent in how they can make websites more interesting and accessible, but they can also cause performance issues. To ensure the best experience for your beloved users, make sure your images:</p>
<ol>
<li><p>are in WebP format.</p>
</li>
<li><p>are compressed.</p>
</li>
<li><p>are responsive.</p>
</li>
<li><p>are lazy-loaded</p>
</li>
<li><p>have width and height attributes.</p>
</li>
</ol>
<h2 id="heading-the-denouement">The Denouement</h2>
<p>There you have it! I hope this information was helpful for all y'all using images to enhance your site's experience for your users.</p>
<p>Have a question about how to implement any of the above recommendations? Or maybe you've caught something you think is missing or incorrect? If any of this sounds like you, comment down below so we can chat about it!</p>
<p><a target="_blank" href="https://github.com/nikkistorme/image-optimizer">My Node image optimizer</a></p>
<p><a target="_blank" href="https://developer.chrome.com/docs/lighthouse/overview/">Google Lighthouse</a></p>
]]></content:encoded></item></channel></rss>