<?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[webdev.tips]]></title><description><![CDATA[Awesome tips and tricks about JavaScript, TypeScript, CSS, Vue, React, and all things webdev.]]></description><link>https://webdev.tips</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1675982212491/R5m5FdGwa.png</url><title>webdev.tips</title><link>https://webdev.tips</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 20:22:19 GMT</lastBuildDate><atom:link href="https://webdev.tips/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[When to use the id attribute]]></title><description><![CDATA[The id attribute is arguably one of the most popular attributes used in HTML, second only to the class attribute. It serves a lot of purposes, which means it can easily be misunderstood or misused. Let's take a look at what it's really for, what it's...]]></description><link>https://webdev.tips/when-to-use-the-id-attribute</link><guid isPermaLink="true">https://webdev.tips/when-to-use-the-id-attribute</guid><category><![CDATA[HTML5]]></category><category><![CDATA[HTML]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Joel Saupe]]></dc:creator><pubDate>Wed, 15 Feb 2023 21:29:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676496343527/fd11ff18-4471-4140-bc26-3600d0fb43ac.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id"><code>id</code> attribute</a> is arguably one of the most popular attributes used in HTML, second only to the <code>class</code> attribute. It serves a lot of purposes, which means it can easily be misunderstood or misused. Let's take a look at what it's really for, what it's not for, and what alternatives might be better.</p>
<h2 id="heading-when-to-use-the-id-attribute">When to use the <code>id</code> attribute</h2>
<h3 id="heading-associate-a-button-with-a-form">Associate a button with a form</h3>
<p><a target="_blank" href="https://webdev.tips/how-to-render-a-form-with-an-external-submit-button">A form's submit button doesn't need to be nested inside the <code>form</code> element</a> if the <code>form</code> element is assigned a unique <code>id</code> and the <code>button</code> has a <code>form</code> attribute with the same value.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nameForm"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"surname"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

...

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">form</span>=<span class="hljs-string">"nameForm"</span>&gt;</span>
  Submit the form
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<h3 id="heading-associate-a-label-with-an-input">Associate a label with an input</h3>
<p>Every <code>input</code> element needs a companion <code>label</code> input. It's not enough to just render text near the input though, the label must be properly associated with the input so that browsers know they're associated. This doesn't just help screen readers, the label text is part of the focus area for the input so users expect to be able to click on the label text to focus on the input. This UX is provided for free as the input is rendered inside the label or if the input has a unique <code>id</code> and the label has a <code>for</code> attribute with the same value.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"givenNameField"</span>&gt;</span>
  Given name
<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"givenNameField"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> /&gt;</span>
</code></pre>
<h3 id="heading-indicate-an-element-should-be-the-accessible-name-of-another">Indicate an element should be the accessible name of another</h3>
<p>Using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby">aria-labeledby</a> on an element allows you to indicate that another element's contents should be considered the <a target="_blank" href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a> for the element. Screen readers use this when announcing an element to the user. This is similar to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label">aria-label</a> but <code>aria-labeledby</code> takes the highest precedence and should be preferred over <code>aria-label</code> so long as the appropriate text is rendered somewhere on the page.</p>
<p>The value of <code>aria-labeledby</code> can be a space-delimited list of IDs, so it's possible for the label to be comprised of multiple elements.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"formTitle"</span>&gt;</span>Favorite color<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">aria-labeledby</span>=<span class="hljs-string">"formTitle"</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<h3 id="heading-indicate-an-element-should-be-the-accessible-description-of-another">Indicate an element should be the accessible description of another</h3>
<p>Using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby"><em>aria-describedby</em></a> on an element allows you to indicate that another element's contents describe the element. While it sounds similar to <code>aria-labeledby</code>, <code>aria-describedby</code> is intended to be used for longer descriptions. This is similar to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-description">aria-description</a> but <code>aria-describedby</code> takes the highest precedence and should be preferred over <code>aria-label</code> so long as the appropriate text is rendered somewhere on the page.</p>
<p>The value of <code>aria-describedby</code> can be a space-delimited list of IDs, so the description can be comprised of multiple elements.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"formDescription"</span>&gt;</span>
  Let us know what your favorite color is so we can send you some sweet swag!
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">"formDescription"</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<h3 id="heading-indicate-an-element-contains-detailed-information-about-another">Indicate an element contains detailed information about another</h3>
<p>Using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-details">aria-details</a> on an element allows you to indicate that another element's contents contain additional details about it. This is very similar to <code>aria-describedby</code> the difference being that <code>aria-details</code> is intended to be used when the describing element contains more complex and structured content, such as lists, images, links, etc., and <code>aria-describedby</code> is intended to be used with a text-only description.</p>
<p>The value of <code>aria-details</code> can be a space-delimited list of IDs, so the detailed description can be comprised of multiple elements.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">aria-details</span>=<span class="hljs-string">"finePrint"</span>&gt;</span>
  Purchase
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"finePrint"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Purchasing this means that lorem ipsum whatever...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Known side effects<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</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">li</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">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"More information"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</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">a</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">li</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">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">li</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">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">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h3 id="heading-indicate-that-interacting-with-an-element-affects-another">Indicate that interacting with an element affects another</h3>
<p>In dynamic web pages, it's common to have buttons with cause-and-effect behavior that affects another element on the page. Clicking a button may make the content change somewhere else on the page, or maybe it makes a dialog appear. You can formalize this relationship by using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls">aria-controls</a> on the trigger element so that screen readers are able to relay this intent to the end user.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">aria-controls</span>=<span class="hljs-string">"infoDialog"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"openDialog"</span>&gt;</span>
  Open dialog
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"infoDialog"</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>
</code></pre>
<h3 id="heading-indicate-that-an-element-contains-an-error-message-related-to-an-input">Indicate that an element contains an error message related to an input</h3>
<p>Most custom form designs don't rely on the browser's native error handling. Instead, input error messages are rendered somewhere (hopefully near) the <code>input</code> it's related to. <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-errormessage">aria-errormessage</a> should be used on the associated <code>input</code> element so that screen readers can properly relay this information to the user. Typically this should be used alongside <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid">aria-invalid</a>.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"nameInput"</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span>
  <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
  <span class="hljs-attr">id</span>=<span class="hljs-string">"nameInput"</span>
  <span class="hljs-attr">aria-invalid</span>=<span class="hljs-string">"true"</span>
  <span class="hljs-attr">aria-errormessage</span>=<span class="hljs-string">"nameError"</span>
/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nameError"</span>&gt;</span>
  Sorry! We don't support names with emoji characters 😭
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</code></pre>
<h3 id="heading-suggest-an-alternative-navigation-flow-through-the-page">Suggest an alternative navigation flow through the page</h3>
<p>Sometimes content on the page isn't in sequential order. Sometimes there is no real sequential order and multiple sections on the page could be viewed in any order. You can suggest an alternative flow through a page by using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-flowto">aria-flowto</a> on an element to suggest one or more other elements that could be viewed next. Screen readers that support this won't force the flow onto the user, instead, they will suggest an alternative flow to the user and let the user decide. <a target="_blank" href="https://a11ysupport.io/tech/aria/aria-flowto_attribute">Support for <code>aria-flowto</code> isn't great</a> as of right now, but it can be worth the minor effort of providing a great user experience.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">aria-flowto</span>=<span class="hljs-string">"description details reviews"</span>&gt;</span>
  Super awesome product
<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"description"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Description<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"details"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>More details<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"Reviews"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Reviews<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<h3 id="heading-indicate-that-an-element-should-be-treated-as-the-parent-of-another">Indicate that an element should be treated as the parent of another</h3>
<p>A parent-child relationship is an important part of the DOM hierarchy. Special design considerations and sometimes limited having limited control of the final DOM hierarchy can mean that you can't always render an element within the element where it should be placed. If this is the case and you need to modify the accessibility tree of a page you can override the interpreted hierarchy by setting <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-owns">aria-owns</a> on the parent element to indicate that another element should be viewed as a direct child of that element. This should only be used when you don't have access to modify the DOM structure as it's preferred you just restructure the DOM if possible.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dl</span> <span class="hljs-attr">aria-owns</span>=<span class="hljs-string">"additionalTerm additionalDescription"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dt</span>&gt;</span>Term<span class="hljs-tag">&lt;/<span class="hljs-name">dt</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dd</span>&gt;</span>Value<span class="hljs-tag">&lt;/<span class="hljs-name">dd</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dl</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">dt</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"additionalTerm"</span>&gt;</span>Another term<span class="hljs-tag">&lt;/<span class="hljs-name">dt</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dd</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"additionalDescription"</span>&gt;</span>Another value<span class="hljs-tag">&lt;/<span class="hljs-name">dd</span>&gt;</span>
</code></pre>
<p>This example is the equivalent accessibly to:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dl</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dt</span>&gt;</span>Term<span class="hljs-tag">&lt;/<span class="hljs-name">dt</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dd</span>&gt;</span>Value<span class="hljs-tag">&lt;/<span class="hljs-name">dd</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dt</span>&gt;</span>Another term<span class="hljs-tag">&lt;/<span class="hljs-name">dt</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dd</span>&gt;</span>Another value<span class="hljs-tag">&lt;/<span class="hljs-name">dd</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dl</span>&gt;</span>
</code></pre>
<h3 id="heading-allow-users-to-navigate-to-a-specific-place-on-a-page">Allow users to navigate to a specific place on a page</h3>
<p>Anchor links allow you to link to a specific location within a page. This can be done by setting the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web#fragment">fragment identifier</a> in the URL to match the <code>id</code> of the element you want to link to. Since the fragment identifier is exposed to the end user through the URL, it's a good idea to ensure the target element's <code>id</code> is readable and intuitive. This can be useful in building a table of contents and allowing users to share a specific part of the page.</p>
<h2 id="heading-when-not-to-use-the-id-attribute">When not to use the <code>id</code> attribute</h2>
<p>With so many good use cases for the <code>id</code> attribute, it's a good idea to avoid using it if doesn't solve one of the use cases listed above to avoid confusing other engineers and potentially creating a worse experience for the user.</p>
<h3 id="heading-avoid-using-id-as-a-css-selector">Avoid using <code>id</code> as a CSS selector</h3>
<p>The <code>id</code> can also be used as a CSS selector, like so:</p>
<pre><code class="lang-css"><span class="hljs-selector-id">#header</span> {
  <span class="hljs-attribute">background</span>: rebeccapurple;
}
</code></pre>
<p>While this is valid, it can create some issues down the road that should be considered.</p>
<h4 id="heading-high-specificity">High Specificity</h4>
<p>It turns out that the "C" for "cascading" in CSS isn't the final decider regarding what rules apply to what elements. <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">Specificity</a> is also taken into account, and as it turns out <code>id</code> selectors have the highest specificity. This means that any rules declared using an id selector will have a higher priority over rules declared with other selectors, even if those rules are declared later on.</p>
<h4 id="heading-low-reusability">Low reusability</h4>
<p>Since an <code>id</code> must be unique to each page; any style rules applied using an <code>id</code> selector can only apply to a single element (and its children) at a time. This doesn't promote reusability as those styles are limited to a single use case.</p>
<h4 id="heading-what-to-use-instead">What to use instead</h4>
<p>Prefer using class names or <a target="_blank" href="https://webdev.tips/styling-using-data-attributes">data attributes as CSS selectors</a>.</p>
<h3 id="heading-avoid-using-id-as-a-javascript-selector">Avoid using <code>id</code> as a JavaScript selector</h3>
<p>It's possible to select an element in JavaScript by referencing the element's <code>id</code> like so:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'elementId'</span>)
</code></pre>
<p>While this is valid, it discourages reusability as the JavaScript code is forced to be tightly coupled to the DOM structure and it's not possible to re-use the code on multiple elements at the same time if necessary.</p>
<h4 id="heading-what-to-use-instead-1">What to use instead</h4>
<p>Prefer using unique data attributes as JavaScript selectors instead. This provides more flexibility and improves code readability as the data attribute can have a clear and descriptive name.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'[data-vue-app]'</span>)
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'[data-vue-app="myAppName"]'</span>)
</code></pre>
<h3 id="heading-avoid-using-id-as-a-selector-in-tests">Avoid using <code>id</code> as a selector in tests</h3>
<p>Quality tests should avoid being tightly coupled to the HTML and DOM structure of the page; they should instead focus on testing the experience from the eyes of the user. It's possible for someone to come along, see an <code>id</code> on an element, and not see any accessibility advantage to having the <code>id</code> and remove it. This could break tests they didn't know about despite not making a change that affects the end-user experience.</p>
<h4 id="heading-what-to-use-instead-2">What to use instead</h4>
<p>Most testing frameworks have support for some type of "test id" such as <code>data-testid</code>. Refer to your testing library's best practices, but use a unique data attribute as a selector to improve code readability.</p>
]]></content:encoded></item><item><title><![CDATA[What state should be stored in the URL]]></title><description><![CDATA[The URL can be a powerful tool for sharing and managing the state of an app, but it's often overlooked or misunderstood. When properly done, the typical CRUD app with read/write/edit/delete functionality doesn't need much more state management than t...]]></description><link>https://webdev.tips/what-state-should-be-stored-in-the-url</link><guid isPermaLink="true">https://webdev.tips/what-state-should-be-stored-in-the-url</guid><category><![CDATA[State Management ]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Joel Saupe]]></dc:creator><pubDate>Tue, 14 Feb 2023 22:36:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676413958860/f5d9e998-ed67-4461-a0aa-79cacad82590.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The URL can be a powerful tool for sharing and managing the state of an app, but it's often overlooked or misunderstood. When properly done, the typical CRUD app with read/write/edit/delete functionality doesn't need much more state management than the URL. Understanding the anatomy of the URL and what type of state should live where can help you build a better user experience.</p>
<h2 id="heading-the-anatomy-of-a-url">The anatomy of a URL</h2>
<p>URL stands for Uniform Resource Locator, which is a standard way to identify the location of a resource on the web.</p>
<p>A URL consists of five distinct parts:</p>
<ol>
<li><p>Protocol</p>
</li>
<li><p>Authority</p>
</li>
<li><p>Path</p>
</li>
<li><p>Query</p>
</li>
<li><p>Fragment</p>
</li>
</ol>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL">Check out this writeup on MDN docs if you're interested in learning about these parts in depth</a>, but for state management purposes we primarily care about <code>path</code>, <code>query</code>, and <code>fragment</code>.</p>
<h3 id="heading-path">Path</h3>
<p>The path is a forward slash "/" delimited address of where a resource resides.</p>
<p>An example of a path is</p>
<pre><code class="lang-plaintext">/blog/2023/01/01/name-of-article.html
</code></pre>
<p>Having a well-organized structure for managing paths can provide users with a better experience as it allows them to understand where they're located and potentially help them see where else they're able to go. It also makes it easier during development to generate links to other resources.</p>
<h4 id="heading-every-part-of-a-path-should-be-accessible">Every part of a path should be accessible</h4>
<p>The structure of the URL path is a hint to the user of not just what resource they're currently viewing, but what resources are also available. A user should be able to access each part or layer of the path and be able to view that part of the resource.</p>
<p>With the <code>/blog/2023/01/01/name-of-article.html</code> example, a user would expect to be able to read the article. The path also exposes information to the user that they should be able to access other articles published on a particular day, month, or year. If the <code>/name-of-article.html</code> is removed and the user navigates to <code>/blog/2023/01/01</code> then the user would expect to see a list of all articles published on that day, navigating to <code>/blog/2023/01</code> would be expected to show a list of all articles published that month and visiting <code>/blog</code> would be expected to show a list of all articles published.</p>
<p>If you have a path like <code>/users/12345/settings</code> , you should also make sure you appropriately render content for <code>/users</code> and <code>/users/12345</code> in addition to <code>/users/12345/settings</code>.</p>
<h4 id="heading-paths-should-be-consistent-and-predictable">Paths should be consistent and predictable</h4>
<p>If every part of a path is accessible, then it's important to ensure that every path is organized consistently and is predictable.</p>
<p>If <code>/users/12345/settings</code> displays the settings for user 12345, and <code>/users/12345</code> displays a summary of user 12345, and <code>/users</code> displays a list of all users; then a user might be confused if they were to see <code>/11111/product/inventory</code>.</p>
<p>A good structure to follow is:</p>
<pre><code class="lang-plaintext">/&lt;resource-name&gt;/&lt;resource-id&gt;/&lt;feature-or-association&gt;/&lt;association-id&gt;
</code></pre>
<p>Some examples:</p>
<pre><code class="lang-plaintext">/products/1234/variants/5678
/products/1234
/products/1234/variants
/products
</code></pre>
<h3 id="heading-query-parameters">Query Parameters</h3>
<p>While the path generally represents information related to <em>what</em> resource is meant to be displayed, the query represents how the <em>resource</em> should be displayed. The query is where resource modifiers and application state should be stored. This includes things like filters, search terms, and pagination info but can also include meta information used for analytics purposes. By default, HTML forms will also add form values to the query after a user submits a form.</p>
<p>Query parameters begin with a question mark (?) and consist of a key/value pair with an equal sign (=) separating the key and the value, and an ampersand "?" in between each pair.</p>
<p>For example:</p>
<pre><code class="lang-plaintext">/resources?pageNumber=4&amp;searchTerm=books&amp;maxPrice=10000
</code></pre>
<h4 id="heading-store-as-much-application-state-in-the-query-as-possible">Store as much application state in the query as possible</h4>
<p>There are a lot of benefits to the user when application state lives in the URL, for example, they can refresh the page without losing state, they can bookmark the page and access it later, or share it with someone else and make sure the other person sees the same thing they do. Any state that modifies the resource defined in the path should be stored in the URL as a query parameter.</p>
<p>A good rule of thumb is that if the user has access to modify the state, and that state is not persisted to the server via an API request, then it should be stored in the URL as a query parameter.</p>
<h3 id="heading-fragment">Fragment</h3>
<p>A fragment appears at the end of the URL and is prefixed with a hashtag (#). This is used in what is referred to as "anchor links" or "jump links", and is used to focus on a particular element on the page. If the page has an element with an <code>id</code> matching the fragment in the URL, then the page is rendered with that element at the top of the screen. This is particularly useful for things like blogs or pages with lots of information as users can be directed to the most relevant place within a page instead of forcing them to find it on their own.</p>
<p>This page assigns an <code>id</code> to each section title, meaning you can navigate to <a target="_blank" href="https://webdev.tips/what-state-should-be-stored-in-the-url#fragment">https://webdev.tips/what-state-should-be-stored-in-the-url#fragment</a> and the page will render with this section at the very top.</p>
<h4 id="heading-use-human-readable-ids-for-fragments-that-are-exposed-to-users">Use human-readable IDs for fragments that are exposed to users</h4>
<p>Fragments should be readable and understandable to the average user, seeing a link like <a target="_blank" href="https://webdev.tips/what-state-should-be-stored-in-the-url#sdfsd2342342fsdaf23423">https://webdev.tips/what-state-should-be-stored-in-the-url#sdfsd2342342fsdaf23423</a> doesn't promote confidence as it doesn't tell the user where they'll navigate to if they were to click on it.</p>
<h4 id="heading-ensure-fragments-work-as-expected">Ensure fragments work as expected</h4>
<p>The browser typically handles navigating to fragments automatically, but if you're using a frontend framework then there's a chance your setup doesn't support fragments. Make sure to check that they work as expected.</p>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ol>
<li><p>The path should include the "what" that's rendered.</p>
</li>
<li><p>Query parameters should include all user-defined modifications of the "what" that's rendered.</p>
</li>
<li><p>The fragment should allow users to jump to specific places within the page.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[How to sort arrays reliably in TypeScript]]></title><description><![CDATA[Let's say you have an array of numbers and need to sort them; it's easy to just call .sort() and be on your way, like this:
const numbers = [10, 25, 2, 4, 1, 100, 6]

numbers.sort()

You would probably assume that since the values are numbers, it wou...]]></description><link>https://webdev.tips/how-to-sort-arrays-reliably-in-typescript</link><guid isPermaLink="true">https://webdev.tips/how-to-sort-arrays-reliably-in-typescript</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[sorting]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Joel Saupe]]></dc:creator><pubDate>Mon, 13 Feb 2023 16:43:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676306523466/046869b9-eef3-4fd8-a19e-61a66049cc54.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's say you have an array of numbers and need to sort them; it's easy to just call <code>.sort()</code> and be on your way, like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> numbers = [<span class="hljs-number">10</span>, <span class="hljs-number">25</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">1</span>, <span class="hljs-number">100</span>, <span class="hljs-number">6</span>]

numbers.sort()
</code></pre>
<p>You would probably assume that since the values are numbers, it would sort them from smallest to biggest, right? Unfortunately, that's not the case and you end up with:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">console</span>.log(numbers)

<span class="hljs-comment">// expected: [1, 2, 4, 6, 10, 25, 100]</span>
<span class="hljs-comment">// actual: [1, 10, 100, 2, 25, 4, 6]</span>
</code></pre>
<p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description">sort method's default comparison function</a> converts values to strings before comparing them. So it converts the numbers to strings and then compares them alphabetically.</p>
<p>Since the default sort comparison converts values to strings and then compares them, you'd assume that it's safe to rely on it when sorting an array of strings, right? Unfortunately, that's not the case here either:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> letters = [<span class="hljs-string">'a'</span>, <span class="hljs-string">'A'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'B'</span>]

letters.sort()
<span class="hljs-comment">// expected: ['a', 'A', 'b', 'B']</span>
<span class="hljs-comment">// actual: ['A', 'B', 'a', 'b']</span>
</code></pre>
<p>The default compare function sorts strings based on their <a target="_blank" href="https://string-functions.com/encodingtable.aspx?encoding=1200&amp;decoding=20127">UTF-16 values</a>. This means that all uppercase characters are sorted before lowercase characters, and characters with accents are also sorted after characters without accents.</p>
<h2 id="heading-customizing-how-an-array-is-sorted">Customizing how an array is sorted</h2>
<p>Since the default way arrays are sorted probably isn't what you want in practice, let's see how we can customize it so that it works the way you'd expect.</p>
<p>The <code>.sort()</code> method accepts a single parameter, which is called a <em>compare function</em>. This compare function should expect to be passed two values from the array and should return a number that indicates how those two items should be sorted. The <code>.sort()</code> method then calls this compare function to compare each item in the array to properly sort it.</p>
<p>If you were to define a type for the compare function, it would look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> SortCompareFunction&lt;ArrayItemType&gt; {
  (firstItem: ArrayItemType, secondItem: ArrayItemType): <span class="hljs-built_in">number</span>
}
</code></pre>
<p>The way that the items are sorted depends on the number that the compare function returns and is based on if it's 0, positive, or negative.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Return Value</td><td>Sort Order</td></tr>
</thead>
<tbody>
<tr>
<td>Number greater than zero (&gt; 0)</td><td>First item should appear first</td></tr>
<tr>
<td>Number less than zero (&lt; 0)</td><td>Second item should appear first</td></tr>
<tr>
<td>Number equals zero (=== 0)</td><td>Maintain original sort order</td></tr>
</tbody>
</table>
</div><h3 id="heading-how-to-sort-numbers">How to sort numbers</h3>
<p>Since the compare function is expected to return a number, you can just subtract one number from the other and return the difference. If the values are the same then it'll return 0, if the first is bigger, it will return a positive number, otherwise, it will return a negative number.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compareNumbersInAscendingOrder</span>(<span class="hljs-params">firstNumber: <span class="hljs-built_in">number</span>, secondNumber: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">return</span> firstNumber - secondNumber
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compareNumbersInDescendingOrder</span>(<span class="hljs-params">firstNumber: <span class="hljs-built_in">number</span>, secondNumber: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">return</span> secondNumber - firstNumber
}

numbers.sort(compareNumbersInAscendingOrder)
<span class="hljs-comment">// output: [1, 2, 4, 6, 10, 25, 100]</span>

numbers.sort(compareNumbersInDescendingOrder)
<span class="hljs-comment">// output: [100, 25, 10, 6, 4, 2, 1]</span>
</code></pre>
<h3 id="heading-how-to-sort-strings">How to sort strings</h3>
<p>Strings come with their own <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare"><code>localeCompare</code> method</a> which can be used to sort strings how you might expect. It's based on the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#options"><code>Int.Collator</code> constructor</a> and accepts all the same options so that you can further customize how strings are compared.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compareStringsInAscendingOrder</span>(<span class="hljs-params">firstString: <span class="hljs-built_in">string</span>, secondString: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-comment">// Set the locale based on the string's locale, otherwise set to undefined to use the browser's locale.</span>
  <span class="hljs-keyword">return</span> firstString.localeCompare(secondString, <span class="hljs-string">'en'</span>, { caseFirst: <span class="hljs-string">'upper'</span> })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compareStringsInDescendingOrder</span>(<span class="hljs-params">firstString: <span class="hljs-built_in">string</span>, secondString: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> secondString.localeCompare(firstString, <span class="hljs-string">'en'</span>, { caseFirst: <span class="hljs-string">'upper'</span> })
}

letters.sort(compareStringsInAscendingOrder)
<span class="hljs-comment">// output: ["A", "a", "B", "b"] </span>

letters.sort(compareStringsInDescendingOrder)
<span class="hljs-comment">// output: ["b", "B", "a", "A"]</span>
</code></pre>
<p>Depending on how often you're sorting and how big the array is that you're sorting, you may run into some performance issues since each time the <code>localeCompare</code> is called a new instance of the <code>Intl.Collator</code> is created. You can resolve this by creating your own instance of the <code>Intl.Collator</code> once and then just referencing it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> enCollator = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.Collator(<span class="hljs-string">'en'</span>, { caseFirst: <span class="hljs-string">'upper'</span> })

hugeListOfWordsThatNeedSorting.sort(enCollator.compare)
</code></pre>
<h2 id="heading-reusing-compare-functions">Reusing compare functions</h2>
<p>While the examples above declare the compare functions on their own, it's more common to see the compare function declared directly inside the <code>.sort()</code> method as an anonymous function like so:</p>
<pre><code class="lang-typescript">numbers.sort(<span class="hljs-function">(<span class="hljs-params">firstNumber, secondNumber</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> firstNumber - secondNumber
})
</code></pre>
<p>This can be good for one-off and shorter compare functions, but if you're doing a lot of sorting it can be beneficial to share compare functions from a common location so they can be re-used throughout your app since there's a good chance that many of them can be highly reusable.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { byNumberAsc } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/utils/sort'</span>

numbers.sort(byNumberAsc)
</code></pre>
<h2 id="heading-eslint-rule">Eslint rule</h2>
<p>If you use <a target="_blank" href="https://eslint.org/">ESLint</a> with <a target="_blank" href="https://typescript-eslint.io/">typescript-eslint</a>, there's actually a rule called <a target="_blank" href="https://typescript-eslint.io/rules/require-array-sort-compare/"><code>require-array-sort-compare</code></a> you can enable to help make sure you never forget to provide a compare function when sorting. It can be easy to forget and have your app work locally when testing with trivial data only to find out things don't sort properly for real users with real data.</p>
]]></content:encoded></item><item><title><![CDATA[How to render a form with an external submit button]]></title><description><![CDATA[There should be at least two ways for any user to submit a form. Pointer users should be able to press a submit button, and keyboard users should be able to press the enter key. Both of these UX behaviors are provided natively by the browser, given a...]]></description><link>https://webdev.tips/how-to-render-a-form-with-an-external-submit-button</link><guid isPermaLink="true">https://webdev.tips/how-to-render-a-form-with-an-external-submit-button</guid><category><![CDATA[forms]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[HTML]]></category><category><![CDATA[Accessibility]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Joel Saupe]]></dc:creator><pubDate>Thu, 09 Feb 2023 19:05:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676040841161/aadfa237-96e7-4356-a977-7154f81ec656.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There should be at least two ways for any user to submit a form. Pointer users should be able to press a submit button, and keyboard users should be able to press the <code>enter</code> key. Both of these UX behaviors are provided natively by the browser, given a <code>button</code> with <code>type="submit"</code> (or an <code>input</code> with <code>type="submit"</code>) is rendered somewhere inside a <code>form</code> tag.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"surname"</span> /&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>
    Submit the form
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>Unfortunately, it's not always possible to render the submit button inside the <code>form</code> element. This is especially true if you don't have complete control over the DOM structure, like when using a component library. So you may end up with something like:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"surname"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

...

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>
  Submit the form
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>And then quickly see that neither clicking the submit button nor pressing the <code>enter</code> key submit the form.</p>
<h2 id="heading-form-attributehttpsdevelopermozillaorgen-usdocswebhtmlelementbuttonattr-form"><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-form">form attribute</a></h2>
<p>No worries! While submit buttons automatically associate with an ancestor <code>form</code>, it's possible to explicitly link a submit button with another form using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-form"><code>form</code> attribute.</a> There are just two requirements:</p>
<ol>
<li><p>a unique <code>id</code> must be assigned to the <code>form</code> element</p>
</li>
<li><p>a <code>form</code> attribute must be added to the <code>button</code> with the value of the form's <code>id</code>.</p>
</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nameForm"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"surname"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

...

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">form</span>=<span class="hljs-string">"nameForm"</span>&gt;</span>
  Submit the form
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>But wait!</p>
<p>The form will now submit when pressing the submit button, but pressing the <code>enter</code> key still won't work. This is because the browser still requires a submit button or input to be rendered somewhere inside the <code>form</code> for it to automatically handle form submission when the user presses the <code>enter</code> key. The easiest way to fix this is to render another submit button inside the form and then hide it visually with CSS.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nameForm"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"surname"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

...

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">form</span>=<span class="hljs-string">"nameForm"</span>&gt;</span>
  Submit the form
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Now the form is accessible and you should be able to render that real submit button wherever you need to.</p>
]]></content:encoded></item><item><title><![CDATA[Styling using data attributes]]></title><description><![CDATA[The vast majority of design systems and component libraries organize their styles using class selectors. Generally, this is a good choice, but it's not perfect. Methodologies like BEM are used as a way to organize these classes into component and mod...]]></description><link>https://webdev.tips/styling-using-data-attributes</link><guid isPermaLink="true">https://webdev.tips/styling-using-data-attributes</guid><category><![CDATA[CSS]]></category><category><![CDATA[Design Systems]]></category><category><![CDATA[design system]]></category><dc:creator><![CDATA[Joel Saupe]]></dc:creator><pubDate>Wed, 08 Feb 2023 20:30:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676040872803/a353c943-a671-4150-b51a-af0fe95a6525.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The vast majority of design systems and component libraries organize their styles using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors">class selectors</a>. Generally, this is a good choice, but it's not perfect. Methodologies like <a target="_blank" href="https://getbem.com/">BEM</a> are used as a way to organize these classes into component and modifier classes. The biggest issue with this way of organizing styles is that it's possible for multiple modifier classes to be applied to a single element and there would be no clear way for an engineer to determine which of the modifier classes would take precedence.</p>
<p>If you stumbled upon some code like this, how would you know what color it would render as?</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"Component is-blue is-red"</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>You would have to render it or go to the source to determine for yourself to determine that it would be blue.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.Component</span><span class="hljs-selector-class">.is-red</span> {
  <span class="hljs-attribute">background</span>: red;
}

<span class="hljs-selector-class">.Component</span><span class="hljs-selector-class">.is-blue</span> {
  <span class="hljs-attribute">background</span>: blue;
}
</code></pre>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">Specificity</a> of modifier classes is not typically something that is considered when maintaining a design system and many times the final CSS is generated automatically which means that a minor rearranging of code could cause the final styles to reverse order and you might wake up having the component render as red instead of blue.</p>
<p>While this problem may seem contrived, this is a problem that occurs due to tech debt in real-world applications. As business requirements change, styling requirements can change, and it's very easy for multiple modifier classes of the same "type" to be mistakenly left in or conditionally added. Over time this can make code harder to read as it's non-deterministic; without a deep understanding of the CSS, an engineer can't determine what color the component would be without actually rendering it.</p>
<h2 id="heading-attribute-selectors-to-the-rescue">Attribute selectors to the rescue</h2>
<p>Using attributes instead of classes to manage modifiers helps eliminate this problem and makes the code easier to understand. We can change the styles like so:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.Component</span><span class="hljs-selector-attr">[data-color=<span class="hljs-string">"red"</span>]</span> {
  <span class="hljs-attribute">background</span>: red;
}

<span class="hljs-selector-class">.Component</span><span class="hljs-selector-attr">[data-color=<span class="hljs-string">"blue"</span>]</span> {
  <span class="hljs-attribute">background</span>: blue;
}
</code></pre>
<p>This then means the HTML we stumble upon would look like so:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"Component"</span> <span class="hljs-attr">data-color</span>=<span class="hljs-string">"blue"</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>While it's still technically possible to have multiple <code>data-color</code> attributes on a single element, <a target="_blank" href="https://html.spec.whatwg.org/multipage/syntax.html#attributes-2">it's not considered valid according to the HTML5 standard</a> and many linting tools can be configured to catch this type of error.</p>
<p>Benefits of this approach include:</p>
<ol>
<li><p>Only a single modifier of each type can be declared</p>
</li>
<li><p>Promotes better accessibility as non-data attributes like <code>title</code> and <code>aria-*</code> attributes can be used</p>
</li>
</ol>
<h2 id="heading-performance-considerations">Performance considerations</h2>
<p><a target="_blank" href="https://ecss.benfrain.com/appendix1.html">According to performance benchmarks</a>, class selectors are more performant than attribute selectors, but not by much. I would argue that for the average web application on modern browsers, any performance issues would go unnoticed and be far outweighed by the benefits of having an easier-to-read and easier-to-maintain codebase.</p>
]]></content:encoded></item><item><title><![CDATA[How to limit dependency on 3rd party tools and libraries]]></title><description><![CDATA[NPM and other package managers have made integrating 3rd party libraries easier than ever.
Need to build a UI? There's a package for that.
Need to format time? There's a package for that.
Need to add authentication? There's a package for that.
Nowada...]]></description><link>https://webdev.tips/how-to-limit-dependency-on-3rd-party-tools-and-libraries</link><guid isPermaLink="true">https://webdev.tips/how-to-limit-dependency-on-3rd-party-tools-and-libraries</guid><category><![CDATA[dependency management]]></category><category><![CDATA[webdev]]></category><category><![CDATA[best practices]]></category><dc:creator><![CDATA[Joel Saupe]]></dc:creator><pubDate>Wed, 02 Jan 2019 22:08:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676040896709/90537892-3add-4253-8136-a4487aeb4475.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>NPM and other package managers have made integrating 3rd party libraries easier than ever.</p>
<p>Need to build a UI? There's a package for that.</p>
<p>Need to format time? There's a package for that.</p>
<p>Need to add authentication? There's a package for that.</p>
<p>Nowadays there's open-sourced code that does practically everything. Just <code>npm install</code> it and you're on your way. Simple enough, right?</p>
<p>But what happens when that package you installed stops being maintained or you find out has a security vulnerability? What do you do when a newer, faster, and smaller package is released?</p>
<p>It's easy to allow 3rd party code to weave and integrate itself into your app, and it's only until you need to replace or remove it that you realize it's become more of a weed than a benefit.</p>
<p>Refactoring and changing libraries is eventually inevitable in practically any long-term project. So what can you do to make the migration process easier?</p>
<h2 id="heading-reducing-dependency">Reducing Dependency</h2>
<p>To reduce dependency on a 3rd party library, you can create a single entry point where the 3rd party library is accessed. Then, throughout the app, you can access the local entry point instead of accessing the package directly. The day you decide to migrate, all you have to do is migrate the entry point instead of every instance the package was used.</p>
<p>Wrapping 3rd party libraries in this manner also makes it easier for you to add or modify functionality if something needs to be adjusted.</p>
<p>If the newer library has a different API, or if you just aren't a fan of the API, to begin with, this allows you to create your own custom API while reaping all the rewards of using an existing library.</p>
<h2 id="heading-an-example">An Example</h2>
<p>Let's say there's a package we want to add called <code>text-upper</code> that capitalizes text.</p>
<p>Instead of accessing the package directly, first, we create an entry point in say <code>utils/caseChanger</code>. Usually, it's better to name it something generic or descriptive instead of just naming the entry point the same as the package.</p>
<p>Inside the entry point can be something as simple as:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">import</span> TextUpper <span class="hljs-keyword">from</span> <span class="hljs-string">'text-upper'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TextUpper
</code></pre>
<p>Then anytime we need to upper case text we just access the library through our entry point like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> caseChanger <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/caseChanger'</span>

upperCase.makeUpperCase(<span class="hljs-string">'hello world'</span>)
</code></pre>
<p>Later on, we find an even better package called <code>textMcChanger</code> that not only makes text uppercase but also makes text lowercase too. We <em>definitely</em> want that.</p>
<p>So now all we have to do is modify our entry point to use this new package. And even though the new package has a slightly different API for capitalizing text, we only have to modify our entry point to ensure everywhere we're already capitalizing text doesn't also need to be refactored.</p>
<p>Our migration in the <code>utils/caseChanger</code> might end up looking something like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> betterPackage <span class="hljs-keyword">from</span> <span class="hljs-string">'textMcChanger'</span>

<span class="hljs-comment">// Modify API for backwards compatibility</span>
betterPackage.makeUpperCase = <span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> betterPackage.upper(text, <span class="hljs-literal">true</span>)

<span class="hljs-comment">// Modify API for consistency with old API</span>
betterPackage.makeLowerCase = <span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> betterPackage.lower(text, <span class="hljs-literal">true</span>)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> betterPackage
</code></pre>
<p>Easy enough, right? All we had to do was change this one file and our app instantly reaps the benefits of the new package without all the extra refactoring.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>It won't make sense to wrap every package like this. It would be silly to try to add a wrapper for React as your UI library and then later try to transform Vue to work like React. But I guess you could!</p>
<p>For most other things though, it's a good practice to follow, and you'll thank yourself later once you find the perfect replacement and you don't have to dread the migration.</p>
]]></content:encoded></item></channel></rss>