<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew K</title>
    <description>The latest articles on DEV Community by Andrew K (@thanksboss).</description>
    <link>https://dev.to/thanksboss</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2188010%2F371b3afa-ad14-4b89-ab90-c4238ee7b317.png</url>
      <title>DEV Community: Andrew K</title>
      <link>https://dev.to/thanksboss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thanksboss"/>
    <language>en</language>
    <item>
      <title>Medium Zoom Next - JS library for image zooming</title>
      <dc:creator>Andrew K</dc:creator>
      <pubDate>Mon, 22 Sep 2025 17:13:19 +0000</pubDate>
      <link>https://dev.to/thanksboss/medium-zoom-next-js-library-for-image-zooming-5eg3</link>
      <guid>https://dev.to/thanksboss/medium-zoom-next-js-library-for-image-zooming-5eg3</guid>
      <description>&lt;p&gt;I rewrote and published a library for image zooming like in Medium. It’s Framework agnostic, small, dependencies free, and now (more or less) modern. The original library seemed to be abandoned for a few years now, and I needed some changes in it for my portfolio website, so I took it in my own hands.&lt;/p&gt;

&lt;p&gt;The changes include, but are not limited to, adding a new method to swap zoomed images, updating event handlers to allow pinch-to-zoom on touch screens, rewriting to TypeScript, and modernizing the setup by changing most of the libraries and workflows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SpasiboKojima/medium-zoom-next" rel="noopener noreferrer"&gt;https://github.com/SpasiboKojima/medium-zoom-next&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ll be glad for any feedback!&lt;/p&gt;

&lt;p&gt;A full changelog with some explanations is here:&lt;br&gt;
&lt;a href="https://github.com/SpasiboKojima/medium-zoom-next/pull/2" rel="noopener noreferrer"&gt;https://github.com/SpasiboKojima/medium-zoom-next/pull/2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>html</category>
      <category>frontend</category>
    </item>
    <item>
      <title>7 Easy UX Improvements for your Webapp</title>
      <dc:creator>Andrew K</dc:creator>
      <pubDate>Wed, 18 Jun 2025 09:45:00 +0000</pubDate>
      <link>https://dev.to/thanksboss/7-easy-ux-improvements-for-your-webapp-4mia</link>
      <guid>https://dev.to/thanksboss/7-easy-ux-improvements-for-your-webapp-4mia</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Finishing my series of articles, I decided to wrap it up with a list of simpler tricks from my experience spanning across different aspects of web development, tricks not big enough for a separate article. With their own caveats, these are generally safe to just slap with a few lines of code and a little familiarization of said caveats.&lt;br&gt;
As always, the topics cover the gap designers usually don't cover and developers usually don't notice.&lt;/p&gt;


&lt;h2&gt;
  
  
  Transitions with AutoAnimate
&lt;/h2&gt;

&lt;p&gt;One of the harder things to animate are lists of items, especially when they are spanning across 2 axes. That's where &lt;a href="https://github.com/formkit/auto-animate" rel="noopener noreferrer"&gt;@formkit/auto-animate&lt;/a&gt; shines the most - with a single line of code you can add some much-needed transitions to when an item gets moved, added, or deleted. Transitions that will help in visually indicating to users what exactly has changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0jpktsoyn8wzxu4m1wd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0jpktsoyn8wzxu4m1wd.gif" alt="Simple example of AutoAnimation motion" width="568" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With only &lt;a href="https://bundlephobia.com/package/@formkit/auto-animate@0.8.2" rel="noopener noreferrer"&gt;2.9kB gzipped&lt;/a&gt;, it offers a zero-config, drop-in animation utility that has many use cases, such as animating accordions and input errors, and bundles support not only for Native JS but for React, Solid, Vue, Preact, Svelte, and Angular as well. The transition animation is customizable and respects the &lt;code&gt;prefers-reduced-motion&lt;/code&gt; setting. You can find more on it in the docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;autoAnimate&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./js/autoAnimate.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dropdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;autoAnimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dropdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I personally used it in a few production React apps for accordions, input errors, and lists, and it worked 9/10 times (due to layout specifics).&lt;/p&gt;

&lt;h2&gt;
  
  
  Target size
&lt;/h2&gt;

&lt;p&gt;A topic I have &lt;a href="https://dev.to/thanksboss/fixing-target-area-or-a-fat-finger-problem-1fe8"&gt;a whole article&lt;/a&gt; about is a practice to make interactive elements easy enough to click/touch by increasing their size. There's a WCAG standard, and a good size to aim for is &lt;strong&gt;44x44 CSS pixels&lt;/strong&gt;. There are a number of situations, and in the simplest case with single elements, you can just add necessary extra padding to them, and to counteract the layout shift, you can apply a similar negative margin. With Tailwind it will look like just &lt;code&gt;-m-4 p-4&lt;/code&gt;, and with CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.target-element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33u25o5g0gzfjhswgf9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33u25o5g0gzfjhswgf9z.png" alt="Comparing default and increased target size without affecting the outside layout" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other cases, you might want to include other elements with your target element by using &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;. A situation I've seen many times neglected, leaving tiny checkboxes the only part that can be clicked to toggle them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdzw9t8pbq67v7gqyz16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdzw9t8pbq67v7gqyz16.png" alt="Comparing input's target area without and with labels" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why is it important? Because accessibility is all about making interfaces usable in a wider range of conditions, and these can be not only health-related specifics but also an inconvenient environment, like shaking transport or malfunctioning touch devices.&lt;br&gt;
With just a few lines of code, we can expand the accessibility of our app in these conditions. And of course, it will be just a little more pleasant to use for everyone else.&lt;/p&gt;
&lt;h2&gt;
  
  
  Virtual keyboard shapes
&lt;/h2&gt;

&lt;p&gt;Just like there are multiple types of inputs for collecting different types of data, there are different shapes a virtual keyboard can take to assist in entering this data. Virtual keyboards are used not only on mobile phones and tablets but also on laptops equipped with touchscreens.&lt;br&gt;
Now there are 3 attributes that can actually affect the virtual keyboard presented to the user, and 2 of them are specifically meant for this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input's &lt;code&gt;type&lt;/code&gt; attribute defines the shape of the input and what type of value it expects. While user agents may assume appropriate inputmode based on that with values like &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, and &lt;code&gt;email&lt;/code&gt;, sometimes it may not be exactly what you want. And at least on iOS it handles numeric values differently (notice the lack of emoji button at the bottom).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y1zl3o6myzpn9ar6e4z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y1zl3o6myzpn9ar6e4z.jpg" alt="iOS keyboard with type=" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;
iOS keyboard with type="number"





&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;inputmode&lt;/code&gt; attribute is exactly for this, with values like &lt;code&gt;"tel" | "url" | "email" | "numeric" | "decimal" | "search"&lt;/code&gt; it allows you to specify the appropriate virtual keyboard without affecting anything else.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enterkeyhint&lt;/code&gt; defines what action label (or icon) to present for the enter key on virtual keyboards. It accepts only predefined values like  &lt;code&gt;'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'&lt;/code&gt;, and the actual displayed result depends on user agent implementation. Keep in mind that just like &lt;code&gt;inputmode&lt;/code&gt; this is only a hint - the actual logic for something like &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;previous&lt;/code&gt; will have to be implemented separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3fuirmq0fg9atbzsxjh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3fuirmq0fg9atbzsxjh.jpg" alt="iOS keyboard with inputmode=" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;
inputmode="email" + enterkeyhint="search"




&lt;h2&gt;
  
  
  Keyboard shortcuts
&lt;/h2&gt;

&lt;p&gt;If your interface involves a sequence of repetitive actions, shortcuts are a great way to speed things up. With a lack of standards, it might be a little hard to come up with a combination that would be intuitive to use and easy to remember. With the amount of browsers and OSs, it might be hard to find a combination that hasn't already been taken. Ideally, we would need to match a hotkey that will already be associated with the corresponding action from the previous user's experience. After all, your users are also interacting with dozens of other interfaces besides yours. That is, it seems to be acceptable to override browser/system shortcuts, but with a lot of considerations (to allow remapping or disabling them).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjo3e2mrgp609mz6qz8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjo3e2mrgp609mz6qz8f.png" alt="Notion's list of keyboard shortcuts" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;
Notion's list of keyboard shortcuts





&lt;p&gt;Let's try to cover the most common and generally safe ones:&lt;br&gt;
&lt;code&gt;Esc&lt;/code&gt; - Close modal/dialog, cancel action. &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; supports it out of the box.&lt;br&gt;
&lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;Enter&lt;/code&gt; / &lt;code&gt;Cmd&lt;/code&gt; + &lt;code&gt;Enter&lt;/code&gt; – Submit (send message, submit form, etc.). With the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element, you can get submit on &lt;code&gt;Enter&lt;/code&gt; automatically inside inputs, but you might want to add this manually for &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;. &lt;br&gt;
&lt;code&gt;↑↓&lt;/code&gt; - a little special usecase built in &lt;code&gt;&amp;lt;input type="number" /&amp;gt;&lt;/code&gt;, make sure you don't lose it if you are handling numbers. &lt;br&gt;
&lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;K&lt;/code&gt; / &lt;code&gt;Cmd&lt;/code&gt; + &lt;code&gt;K&lt;/code&gt; - becomes more common to use for search or command palette, seemingly started by Slack. There are libraries for components that come with this shortcut now.&lt;br&gt;
&lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;/&lt;/code&gt; – Also command palette or help menu.&lt;br&gt;
Formatting hotkeys (&lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;I&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;U&lt;/code&gt;) - some text editor libraries already come with them. Usually these are okay to override reserved combinations, assuming that the user easily unfocuses the editor to lose the override.&lt;/p&gt;

&lt;p&gt;Now you can actually add event listeners to the whole page or just to the element you want your shortcuts to take effect in. Make sure to handle both Windows and macOS special keys. &lt;code&gt;e.preventDefault()&lt;/code&gt; here is to stop the default behavior of the hotkey.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metaKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctrlKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;openSearchBar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;onKeyDown&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metaKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctrlKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;openSearchBar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  View Transitions
&lt;/h2&gt;

&lt;p&gt;With hardware advancement and increasing internet speed, web apps are also bulkier and can still have a noticeable loading process for switching between pages, usually accompanied by an empty screen. To reduce perceived loading latency, we can add a smooth transition there, and to do that, the easiest way would be to use the View Transitions API.&lt;br&gt;
View Transitions can be used for both MPA to transition between pages and SPA for changes to parts of the DOM. It ensures that any element that’s getting unchanged stays exactly in place, with no blinks, and the changing elements transition in a crossfade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp89bb5r6ocxwde0our3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp89bb5r6ocxwde0our3.webp" alt="Page navigation with View Transitions" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View Transitions are &lt;a href="https://caniuse.com/?search=View%20Transitions" rel="noopener noreferrer"&gt;supported by most of the browsers&lt;/a&gt; (82% at the time), and you can start using them for page navigation today as progressive enhancement. These 2 lines of code are all you need for a start, before you decide to customize the transition animation and manipulate the view transition itself which the API also allows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@view-transition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;navigation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Skip links
&lt;/h2&gt;

&lt;p&gt;The standard way of putting navigation links on the web is at the very top of every page, right before the main content. Where a mouse or touchscreen user could just point straight down, a keyboard user has to grind through this repeated content that can have dozens of entries before they can get to the primary part. For the purpose of getting straight to the main part, a lot of pages have Skip Links placed before the header (dev.to also has a skip link, try reloading the page and pressing tab). It makes sense to add it if your header has, say, 4+ elements in it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u4g24psvzng0r6cx62j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u4g24psvzng0r6cx62j.png" alt="Focused Skip Link on dev.to" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Skip Link is just an anchor tag visually hidden that targets an existing ID on the current page. It should be positioned as the first focusable element inside the body and header.&lt;/p&gt;

&lt;p&gt;You can even have more than one Skip Link positioned one after another if you have a particularly lengthy content.&lt;/p&gt;

&lt;p&gt;Corresponding WCAG rule - &lt;a href="https://www.w3.org/TR/WCAG/#bypass-blocks" rel="noopener noreferrer"&gt;2.4.1 Bypass Blocks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For correct visual hiding, there are a few catches that are &lt;a href="https://www.tpgi.com/the-anatomy-of-visually-hidden/" rel="noopener noreferrer"&gt;explained here&lt;/a&gt;, and with the recommended pattern at the end, it will be more robust than simple absolute positioning with offsetting.&lt;/p&gt;

&lt;p&gt;Tailwind offers the &lt;code&gt;sr-only&lt;/code&gt; class implementing this pattern (with the name being a little off since it's not only for screen readers) that you would need to prepend with &lt;code&gt;focus:&lt;/code&gt;, or you can use the plain CSS version below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#main"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"skip-link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Skip to main content
  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Other header content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Main page content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.skip-link&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:active&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  PWA Shortcuts
&lt;/h2&gt;

&lt;p&gt;Just like native apps, PWAs can be installed on mobile and desktop devices, which will give the app access to more device-specific APIs and the users a way to use it quicker with fewer browser distractions. Converting to PWA is as easy as adding a manifest.json file with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable" rel="noopener noreferrer"&gt;a few necessary fields&lt;/a&gt;. With one single addition, we can go a little further and give our users a way to quickly jump to the most commonly used screens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2zxgk7uv3nf4y2wbw8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2zxgk7uv3nf4y2wbw8f.png" alt="Shortcuts list on Windows and Android" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This list of shortcuts is only displayed by right-clicking or long-pressing the app icon, meaning they're only available once the PWA is installed on the user's device.&lt;/p&gt;

&lt;p&gt;To define shortcuts for your PWA, use the &lt;code&gt;shortcuts&lt;/code&gt; property of your app's &lt;code&gt;manifest.json&lt;/code&gt;. This property is an array of objects defining each shortcut's name and URL, as well as the optional short name, description, and icons. For example, here's the web app manifest of a calendar app that defines two shortcuts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shortcuts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/new-event"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"View today's events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/today"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Applying even all of these points won't really cost you anything. So if we can make our apps a little more pleasant, why not do it?&lt;br&gt;
If you feel like something could be presented better, feel free to reach out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inputmode" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inputmode&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.tempertemper.net/blog/skip-links-what-why-and-how?ref=dailydev#main" rel="noopener noreferrer"&gt;https://www.tempertemper.net/blog/skip-links-what-why-and-how?ref=dailydev#main&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Expose_common_actions_as_shortcuts" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Expose_common_actions_as_shortcuts&lt;/a&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Anticipating User Errors in Web</title>
      <dc:creator>Andrew K</dc:creator>
      <pubDate>Thu, 20 Mar 2025 11:00:00 +0000</pubDate>
      <link>https://dev.to/thanksboss/anticipating-user-errors-in-web-3nbc</link>
      <guid>https://dev.to/thanksboss/anticipating-user-errors-in-web-3nbc</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article came to me as a logical continuation of &lt;a href="https://dev.to/thanksboss/fixing-target-area-or-a-fat-finger-problem-1fe8"&gt;my first article&lt;/a&gt;. After finishing it, I kept exploring the topic of UX in general and realized that it's not only hitting targets that our users can struggle with.&lt;br&gt;
This time I'll be talking about how we usually make our interfaces react to various types of incorrect user input (or lack of any) and how it can create even more confusion for our users.&lt;br&gt;
And again, this article covers mostly how we, developers, can contribute to improving this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disabled Buttons
&lt;/h2&gt;

&lt;p&gt;The first thing that comes to mind when thinking about where users can make mistakes is a form element. It's very common for web apps to disable the main submit button until the user fills everything and fills it correctly. This happens on login screens, personal information pages, contact forms, some long onboarding flows, and so on. It may seem like a no-brainer to just set that &lt;code&gt;disabled&lt;/code&gt; attribute until the user passes all the validation, but there can be &lt;a href="https://axesslab.com/disabled-buttons-suck/" rel="noopener noreferrer"&gt;a lot of confusion for users&lt;/a&gt;. It can be that they don’t even know exactly whether it was disabled initially or if it has changed its state to &lt;code&gt;disabled&lt;/code&gt; in response to any of their actions. Most importantly, it makes the button practically non-existent for screen readers and keyboard navigation, because it &lt;strong&gt;can't receive focus&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Usually we disable buttons instead of hiding them when we want to show that they can be enabled at some point, but many times we don't &lt;strong&gt;properly explain&lt;/strong&gt; to our users what should enable it. In most cases they don't even need to be disabled. Exceptions where we could use the attribute could be buttons in loading states, disabled because something is unavailable on our side, or other special cases.&lt;/p&gt;

&lt;p&gt;As Matthew Standage &lt;a href="https://uxdesign.cc/why-heuristics-are-only-rules-of-thumb-the-case-of-the-disabled-button-4824958627e9" rel="noopener noreferrer"&gt;notes&lt;/a&gt;, “when we disable a button on a form we are often disabling the call-to-action — that thing on the page we trying to encourage users to click to proceed with their journey.“&lt;/p&gt;

&lt;p&gt;That's why by default &lt;code&gt;react-hook-form&lt;/code&gt; library is configured in a way that it doesn't validate anything inline until the first submit, suggesting that way to leave the button initially enabled.&lt;/p&gt;

&lt;p&gt;So, the first and simplest solution is to &lt;strong&gt;keep them enabled&lt;/strong&gt; from the beginning. Let users submit their forms, validate their input, and then explicitly explain and point to any errors they have. Сomplementary to this, inline validation on the field's blur event still fits rather well. It's a good practice to inform users orderly and not overload them with information at once only when the submit is made.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/sandrina-p/embed/WNRYabB?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Another less ideal fallback solution - &lt;code&gt;aria-disabled&lt;/code&gt; attribute. &lt;code&gt;disabled&lt;/code&gt; applies a lot of modifications to an element, while &lt;code&gt;aria-disabled&lt;/code&gt; just conveys the semantics of it, meaning that you would need to add some CSS and JS to make it complete. If you absolutely need to have a disabled button, here's &lt;a href="https://css-tricks.com/making-disabled-buttons-more-inclusive/" rel="noopener noreferrer"&gt;a great article by Sandrina Pereira&lt;/a&gt; on how to do it more inclusively with steps on applying &lt;code&gt;aria-disabled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Generally, &lt;code&gt;disabled&lt;/code&gt; attribute can be used for many other cases and elements, but for form buttons &lt;code&gt;aria-disabled&lt;/code&gt; is better. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled" rel="noopener noreferrer"&gt;From MDN&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The header button element associated with non-collapsible accordion panel,&lt;/li&gt;
&lt;li&gt;A button which is important to keep in the page's focus order, but its action is presently unavailable - such as submitting a form,&lt;/li&gt;
&lt;li&gt;Temporarily inactive items in a menu widget that would otherwise be skipped over via standard keyboard navigation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Bonus point: Displaying error messages
&lt;/h3&gt;

&lt;p&gt;Since we are talking about forms and input errors, it should be the right place to figure out how to properly display them. Probably the most popular position to place validation errors is directly below the corresponding field. It might be a good idea to reconsider this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnjeq8ammxozp71mlvtf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnjeq8ammxozp71mlvtf.jpg" alt="Example of errors covered with browser's autocomplete and open tooltip" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;
Google has an error message covered by autocomplete. Inaccurately unfolding element has both input and errors obstructed



&lt;p&gt;&lt;br&gt; In these cases it might be very well obvious what kind of error gets hidden, but in a less common scenario this behavior can become pretty inconvenient.&lt;br&gt;
Let's cover now how to properly present them from an &lt;strong&gt;accessibility standpoint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First of all, to mark an input itself as invalid, we use &lt;code&gt;aria-invalid="true"&lt;/code&gt;, which will make the screen reader &lt;strong&gt;recognize and announce&lt;/strong&gt; that the field is invalid when it’s focused.&lt;br&gt;
Error messages related to input also need to be &lt;strong&gt;connected&lt;/strong&gt; to it, and not only visually, but for screen readers as well. For that, we can use &lt;code&gt;aria-describedby&lt;/code&gt; with an id of the element containing the error message. You can also pass multiple ids with &lt;code&gt;aria-describedby="addressError addressHint"&lt;/code&gt;.&lt;br&gt;
One gotcha:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important to note that although &lt;code&gt;aria-describedby&lt;/code&gt; supports multiple ids, if you change them (or the elements’ content) dynamically while the input is focused, the SR won’t re-announce its new content automatically. It will only read the new content after you leave the input and focus it again.&lt;br&gt;
&lt;a href="https://www.smashingmagazine.com/2023/02/guide-accessible-form-validation/#the-error-message" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, since errors appear dynamically in the form, we need to properly &lt;strong&gt;announce their appearance&lt;/strong&gt; to the screen readers by using &lt;code&gt;aria-live="assertive"&lt;/code&gt;. Otherwise, the SR won’t announce it unless the user manually navigates to it. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that the aria-live attribute must be present in the DOM right from the beginning, even if the element doesn’t hold any message yet, otherwise, Assistive Technologies may not work properly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Borrowing example from the code snippet above, the end result should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"ticketCount"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;span&amp;gt;&lt;/span&gt;Number of&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; Tickets&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ticketCount"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;aria-invalid=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"ticketError"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ticketError"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add between 1 and 9 tickets.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So called Live Regions should be used for other dynamic messages as well, but that's a different broad topic.&lt;br&gt;
From the article mentioned above:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There’s much more to tell you about this little aria-live attribute and the big things it does. There are gotchas as well. For example, if it is applied incorrectly, the attribute can do more harm than good. It’s worth reading &lt;a href="https://bitsofco.de/using-aria-live/" rel="noopener noreferrer"&gt;“Using aria-live”&lt;/a&gt; by Ire Aderinokun and Adrian Roselli’s &lt;a href="https://adrianroselli.com/2020/11/more-accessible-skeletons.html" rel="noopener noreferrer"&gt;“Loading Skeletons”&lt;/a&gt; to better understand how it works and how to use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Navigation errors
&lt;/h2&gt;

&lt;p&gt;I already mentioned my first article in the introduction, which basically covers a similar topic of how to make hitting elements less error-prone and a little more pleasant.&lt;/p&gt;

&lt;p&gt;But there's one more related case I omitted.&lt;br&gt;
&lt;strong&gt;Hover menus&lt;/strong&gt; that unfold with a list of items, a UI pattern that's been tackled many years ago, still fail to deliver an effortless navigation sometimes. It doesn't have to be a menu, sometimes it can be some tooltip with buttons or other selectors that appear only on hover. Essentially, it's a set of controls hidden behind a smaller element to save space that appears on hover, trying to save the user a click.&lt;/p&gt;

&lt;p&gt;Try to open the menus and move your pointer directly to the last item:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/SpasiboKojima/embed/KwKydyq?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Instinctively, we are going with the shortest route when we see our target right in our reach, but apparently the path to it lies beyond the hover area of the whole menu, and we see our target disappear. This is a complication of pointer devices; on a touch device, there won't be any problem at all.&lt;/p&gt;

&lt;p&gt;Of course, for some shapes of menus, you can rely on libraries, but there may be a need to handle more peculiar cases.&lt;br&gt;
There are at least &lt;strong&gt;4 ways&lt;/strong&gt; to deal with this, and you can use a combination of the last 3 to better suit your needs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Click&lt;/strong&gt; — changing menu to open on click is by far the simplest method, whether it's worth all the extra effort that comes with the other methods or you are stopping here is up to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timer&lt;/strong&gt; — setting some delay on pointer events to not close the menu instantly. Implementation is up to you; I left mine simplified for the demo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Safe Triangles&lt;/strong&gt; — a middle ground between relative simplicity and implementing fully dynamic safe triangles. Basically, you add curved invisible (made visible for demo purposes) triangles to the sides of your button. The triangles will make a path to your last submenu items while not obstructing much navigation to other menu items. Inspired by &lt;a href="https://slides.com/wireframe?debug=2#menu" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/SpasiboKojima/embed/mydpzwp?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Let me break down a little the last one. &lt;br&gt;
First, we are placing the safe area inside the submenu and making it the same height. With &lt;code&gt;position: absolute;&lt;/code&gt; we push it outside to cover the width of the button (in my case, the button and submenu have equal width). Unlike with other solutions, you don't need to set any &lt;code&gt;pointer-events: none;&lt;/code&gt;, the triangles themselves will be an active part of your submenu in a shape you already see.&lt;br&gt;
The triangles are drawn with one relatively simple &lt;code&gt;path()&lt;/code&gt; string using &lt;code&gt;clip-path: path();&lt;/code&gt;. In my example the coordinates are hardcoded, but you can use your framework of choice to calculate the values dynamically.&lt;br&gt;
You can use &lt;a href="https://svg-path-visualizer.netlify.app/" rel="noopener noreferrer"&gt;this tool&lt;/a&gt; to have an explanation of each step of the path drawing.&lt;/p&gt;

&lt;p&gt;Setting this dynamically with a template string will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`path("M ${width} 0 S ${width} ${topDistance + 1} , 0 ${topDistance + 1} L ${width} ${topDistance + 1} L ${width} 0 M ${width} ${height} S ${width} ${bottomDistance - 1} , 0 ${bottomDistance - 1} L ${width} ${bottomDistance - 1} L ${width} ${height}")`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt; - width and height of the area (also width of the opening button and height of the unfolding submenu);&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;topDistance&lt;/code&gt; - distance between the top edge of the submenu and the button by Y coordinate;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bottomDistance&lt;/code&gt; - distance between the bottom edge of the submenu and the button by Y coordinate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do a 1 pixel adjustment into the button's area to prevent any small gaps that could appear between them during rounding or other weird behavior of Firefox, which happened to me. Same goes for the &lt;code&gt;right: 99%;&lt;/code&gt; line of &lt;code&gt;.safeAreaElement&lt;/code&gt;, this will leave 1% of the area inside the submenu.&lt;/p&gt;

&lt;ol start="4"&gt;
  &lt;li&gt;
&lt;b&gt;Dynamic Safe Triangle&lt;/b&gt; — the most complicated solution with its own nuances. One Safe Triangle is now dynamically drawn starting from your pointer position. &lt;a href="https://ishadeed.com/article/target-size/#safe-triangle-target-areas" rel="noopener noreferrer"&gt;This is the best implementation&lt;/a&gt; I could find, with interactive examples and code explanations, and I have nothing to add to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Navigation Blocking
&lt;/h3&gt;

&lt;p&gt;A behavioral pattern I feel like used to be more common before, but now I barely see it anywhere. Navigation blocking is supposed to occur when you are filling a form, making a transaction, i.e., in the process of doing a multi-step task, and you have some &lt;strong&gt;unsaved progress&lt;/strong&gt;. It won't be possible, of course, to fully block your navigation. This will just open a &lt;strong&gt;modal dialog&lt;/strong&gt; to double-check that you won't be losing anything important. Traditional implementation would prevent you from &lt;em&gt;accidentally&lt;/em&gt; clicking on a link, hitting the browser's navigation buttons, or even closing the tab or the whole browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbjfltuq0uh09g9u08ua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbjfltuq0uh09g9u08ua.png" alt="Firefox message displayed before leaving the page" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;
Firefox message displayed before leaving the page



&lt;p&gt;&lt;br&gt; The code for this would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beforeunload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isTaskInProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;returnValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event" rel="noopener noreferrer"&gt;MDN on beforeunload event&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, this is something you would do for MPA. For SPA you would &lt;em&gt;ideally&lt;/em&gt; need both listening to the &lt;code&gt;beforeunload&lt;/code&gt; event (to have browser navigation/closure covered) and router-specific logic, since for SPA that's where the majority of routing really happens. Depending on your router of choice, one or both of these cases may already be covered by the library.&lt;/p&gt;

&lt;p&gt;I'll list some implementations in my stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/router/v1/docs/framework/react/guide/navigation-blocking" rel="noopener noreferrer"&gt;Tanstack Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactrouter.com/api/hooks/useBlocker" rel="noopener noreferrer"&gt;React Router's&lt;/a&gt; and &lt;a href="https://reactrouter.com/api/hooks/useBeforeUnload" rel="noopener noreferrer"&gt;useBeforeUnload&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.solidjs.com/solid-router/reference/primitives/use-before-leave" rel="noopener noreferrer"&gt;Solid Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next.js removed it from app router, leaving place only for &lt;a href="https://github.com/vercel/next.js/discussions/47020" rel="noopener noreferrer"&gt;custom solutions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As in many cases, this is one of the techniques that should be used sparingly, and in the next section we’ll be talking about the relevance of such confirm modals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modals on destructive actions
&lt;/h2&gt;

&lt;p&gt;Modal dialogs is another important topic, as they are usually what guarding users from &lt;strong&gt;critical actions&lt;/strong&gt;. Modals in general are a common thing in UI, they can be used for different scoped tasks to take users out of their current context. But a confirmation modal is something that can also be easily overused, leading to creating a habit of automatically confirming them and losing it's meaning in the first place.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In general, users don’t show much annoyance with self-initiated modals, but they get very frustrated with any kind of auto-triggered modals. But if a modal helps users avoid critical mistakes, they find them acceptable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use modal dialogs to avoid an irreversible error/action that will have serious consequences, like deleting or transferring something. This kind of modal should be something they normally &lt;em&gt;don't&lt;/em&gt; do, something to grab their attention and slow them down, letting them consciously process the action taking place.&lt;/p&gt;

&lt;p&gt;When it comes to implementation, we need to consider accessibility with how the focus will be handled when the modal is opened and closed, prevent the underlying page from scrolling, as well as provide obvious ways to close it, like a big enough X button, ESC keyboard button, and clicking outside of it.&lt;br&gt;
Thankfully, modern &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog" rel="noopener noreferrer"&gt;&amp;lt;dialog&amp;gt;&lt;/a&gt; element makes implementing all of it easy, with very little JS needed. It's well supported with global adoption of 96%, customizable, has some more complicated cases like hitting ESC with multiple dialogs open handled, and can be &lt;a href="https://youtu.be/4prVdA7_6u0" rel="noopener noreferrer"&gt;well animated&lt;/a&gt;. As far as I know, all past issues with native dialogs were fixed, and now there's no real reason not to use it. I really like the implementation of modals in &lt;a href="https://daisyui.com/components/modal/" rel="noopener noreferrer"&gt;DaisyUI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/SpasiboKojima/embed/Byadpvm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The better approach to safeguard a critical action is the &lt;strong&gt;undo option&lt;/strong&gt;. By allowing users to rollback their unintended operation, we encourage some to use the app without the fear of breaking something and entrust others with an uninterrupted flow of decisions. It's a good safety net that doesn't get in the way, unlike modals.&lt;br&gt;
Unfortunately, the undo is much harder to implement and requires designing on all levels, while it may not be possible at all in some cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;I wanted to put together all the ways to handle user errors, the ways we can trap them inadvertently sometimes, and the ways to make some improvements here.&lt;br&gt;
The solutions are not always simple. Sometimes it takes more than one safety net, and sometimes it's very easy to overdo it with our efforts.&lt;br&gt;
If you feel like something could be presented better, feel free to reach out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.nngroup.com/articles/slips" rel="noopener noreferrer"&gt;https://www.nngroup.com/articles/slips&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles" rel="noopener noreferrer"&gt;https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.smashingmagazine.com/2024/09/how-manage-dangerous-actions-user-interfaces/#when-to-use-it-2" rel="noopener noreferrer"&gt;https://www.smashingmagazine.com/2024/09/how-manage-dangerous-actions-user-interfaces/#when-to-use-it-2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>html</category>
      <category>ux</category>
    </item>
    <item>
      <title>Fixing Target Size or A Fat Finger Problem</title>
      <dc:creator>Andrew K</dc:creator>
      <pubDate>Mon, 21 Oct 2024 09:35:00 +0000</pubDate>
      <link>https://dev.to/thanksboss/fixing-target-area-or-a-fat-finger-problem-1fe8</link>
      <guid>https://dev.to/thanksboss/fixing-target-area-or-a-fat-finger-problem-1fe8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A few years ago, a client of mine said, "Andrew, these designs are great, but let's make everything twice as large, because, you know, these fat finger Americans will have a hard time clicking that.". It was a long form with lots of small elements, like checkboxes and toggles, and what he said actually made sense and led me to think about what the rules are and to what extent we should adjust our interfaces. Half of the topic is a designer's concern, but as a Frontend developer, I'll be talking about the developer's part and what we can do better while not messing with artistic choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Theory
&lt;/h2&gt;

&lt;p&gt;What is a Fat Finger Error?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A fat finger error is a human error caused by pressing the wrong key when using a computer to input data. &lt;br&gt;
&lt;a href="https://www.investopedia.com/terms/f/fat-finger-error.asp" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The term became broadly known in the finance field after it caused some serious damage on numerous occasions (see examples in the source above). In a consumer app, it can lead to a clunky user experience or rage taps even in a perfect environment and can turn out to be unusable in a non-conventional environment or for users with special needs. And we want to leave the margin for error as little as possible for our users if we want them to keep coming back to our app.&lt;/p&gt;

&lt;p&gt;The term "target" in this topic refers to basically any interactive area in a UI that accepts a pointer action (click, touch). This could be buttons, inputs of every type, navigation links, media controls, etc.&lt;/p&gt;

&lt;p&gt;So, generally, what's the target size requirement? According to &lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/target-size.html" rel="noopener noreferrer"&gt;WCAG 2.5.5 guideline&lt;/a&gt;, it should be &lt;strong&gt;at least 44x44 CSS pixels&lt;/strong&gt; (except for targets in a sentence or block of text, i.e., links inside of text or help icons at the end of it). That's the size we will be targeting whenever possible, the recommended upper bar. As the last resort, according to &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum" rel="noopener noreferrer"&gt;WCAG 2.5.8 Minimum guideline&lt;/a&gt;, we should ensure &lt;strong&gt;a proper spacing&lt;/strong&gt; between targets is set.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Undersized targets (those less than 24 by 24 CSS pixels) are positioned so that if a 24 CSS pixel diameter circle is centered on the bounding box of each, the circles do not intersect another target or the circle for another undersized target;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is, technically, everything bigger than 24x24 pixels is not required to have any spacing from other targets.&lt;/p&gt;

&lt;p&gt;There are other UI design guidelines, of course, which can even use different units of measurement (e.g., &lt;a href="https://developer.apple.com/design/human-interface-guidelines/buttons#Best-practices" rel="noopener noreferrer"&gt;Apple Human Interface Guidelines&lt;/a&gt;, Google Material Design). Let's stick to &lt;a href="https://stackoverflow.com/a/31715400/10360185" rel="noopener noreferrer"&gt;pixels for web&lt;/a&gt;, since with other units &lt;a href="https://www.sebastien-gabriel.com/designers-guide-to-dpi/" rel="noopener noreferrer"&gt;there's a lot going on&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single targets
&lt;/h3&gt;

&lt;p&gt;As we discussed above, even an undersized target with enough spacing around it is considered acceptable. It will be easy to hit on touch devices due to the broad area they can cover with a single touch. With pointer devices, however, it will be a different case since the pointer covers only 1 pixel directly under it. That being said, if we can easily improve user experience and increase target size in this case with no drawbacks, I don't see a reason not to do it.&lt;/p&gt;

&lt;p&gt;To increase the target size, we can simply &lt;strong&gt;add padding&lt;/strong&gt; to it. In certain cases, however, this can shift around the layout, like when your target is part of a smaller flexbox. In such cases, I match the added padding with a negative margin applied to the same side. It's a simple trick that shouldn't really confuse the next developer reading your code, it's easy to calculate if there's another margin that has to be set, and it works well with Tailwind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33u25o5g0gzfjhswgf9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33u25o5g0gzfjhswgf9z.png" alt="Comparing default and increased target size without affecting the outside layout" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;
Increasing target size with padding without affecting the outside layout



&lt;p&gt;&lt;br&gt; The alternative to margins would be &lt;strong&gt;adding a pseudo-element&lt;/strong&gt;, and perhaps in some cases it will be the only way, but usually a padding + margin is more efficient and easier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.target-element&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lists and composite elements
&lt;/h3&gt;

&lt;p&gt;A lot of times in all kinds of navigational lists, spacing gets added to the surrounding container, leaving the items themselves small and harder to pinpoint. Whenever possible, you should &lt;strong&gt;prefer full-width or full-height&lt;/strong&gt; bars for lists. Gaps between items can also be replaced with padding, but it may result in extra spacing around the container. To counteract this, once again, you can apply a negative margin to the container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80b3pzcpta6vdb2r3odz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80b3pzcpta6vdb2r3odz.png" alt="Comparing horizontal navigational list target area with and without padding" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll8hmyiteqlkbi44fatq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll8hmyiteqlkbi44fatq.png" alt="Comparing vertical navigational list target area with and without padding" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For inputs of all types (like checkbox, radio, file, etc.), we can use the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element. In this case, &lt;strong&gt;the target area extends to the label&lt;/strong&gt; as well, which makes a night and day difference when working with small elements like checkboxes.&lt;br&gt;
Quoting &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a user clicks or touches/taps a label, the browser passes the focus to its associated input (the resulting event is also raised for the input). That increased hit area for focusing the input provides an advantage to anyone trying to activate it — including those using a touch-screen device.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can either provide &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;for&lt;/code&gt; attributes to the target element and label respectively, or an easier way would be to nest the target element inside the label.&lt;br&gt;
Elements that can be associated with a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element include &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; (except for type="hidden"), &lt;code&gt;&amp;lt;meter&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdzw9t8pbq67v7gqyz16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdzw9t8pbq67v7gqyz16.png" alt="Comparing input's target area without and with labels" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last obstacles in input's accessibility I saw in various forms are &lt;strong&gt;prefixes/adornments&lt;/strong&gt; that get used with them. An icon or badge that's placed on top of input and not interactive shouldn't obstruct the input's target area. It could be avoided by simply adding the &lt;code&gt;pointer-events: none;&lt;/code&gt; CSS rule or by using &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; as well. Similarly, a label can be used to extend the target area to include an icon positioned near the input when visually it's not clear where the input's target area extends to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb3vihnuqkxbi2l6ujup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb3vihnuqkxbi2l6ujup.png" alt="Comparing input with adornment target area" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Range slider
&lt;/h3&gt;

&lt;p&gt;A common issue I noticed with range sliders is that the target area is limited to its track and thumb, overlooking the area that covers the thumb's height. Some component libraries with custom range slider implementations suffer from that, at least with the setup they come with out of the box. So the first step we should do is &lt;strong&gt;increase the target area to thumb's height&lt;/strong&gt;. We can go even &lt;em&gt;further&lt;/em&gt; by extending the slider's target height beyond the thumb's height, which can be especially useful if your thumb is small.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxgaja59i41yqefxc5x8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxgaja59i41yqefxc5x8.png" alt="Youtube's player target area is bigger then it's thumb" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;
Youtube's player target area



&lt;p&gt;&lt;br&gt;Native HTML5 range slider (range input) supports it out of the box easily. Unfortunately, it's very limited in terms of style options and &lt;a href="https://range-input-css.netlify.app/" rel="noopener noreferrer"&gt;inconsistent across browsers&lt;/a&gt;, so it's hard to recommend it.&lt;/p&gt;

&lt;p&gt;Below I'm comparing 3 common types of target sizes in range sliders. The last one, in my opinion, being ideal in terms of its target size, and I think &lt;em&gt;that's&lt;/em&gt; where we should aim. Of course it's possible that you'll be able to tweak your library of choice to this state, but in the short time I tried with some libraries, I wasn't able to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuut95ejfepdhyq243q0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuut95ejfepdhyq243q0m.png" alt="Comparing small target size of some libraries with native range slider" width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;
Comparison of slider's target area options



&lt;p&gt;&lt;br&gt;You can try it here (ignore the extra styles, and keep in mind that it looks best in Firefox):&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/SpasiboKojima/embed/Exqmopd?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing an existing codebase
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/pesticide-for-chrome/bakpbgckdnepkmkeaiomhmfcnejndkbi?hl=en" rel="noopener noreferrer"&gt;Pesticide for Chrome&lt;/a&gt; / &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/pesticide/" rel="noopener noreferrer"&gt;Pesticide for Firefox&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The easiest way to test manually would be using this browser extension, which simply injects some CSS and adds an outline around all elements on the page. &lt;/p&gt;

&lt;p&gt;You could also, of course, add your own global CSS. Just keep in mind that if you are testing someone else's codebase, you'd never know if some supposedly non-interactive tags were made interactive by previous developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Check out the amazing article by Ahmad Shadeed linked below if you want a deep dive into the topic. He has the best interactive examples I've seen. I wanted to add some things from my experience and also make the topic more approachable and directed to us developers. Hence, I decided to write the first article of my own.&lt;br&gt;
If you feel like something could be presented better, feel free to reach out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ishadeed.com/article/target-size/" rel="noopener noreferrer"&gt;https://ishadeed.com/article/target-size/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.smashingmagazine.com/2022/02/fitts-law-touch-era/" rel="noopener noreferrer"&gt;https://www.smashingmagazine.com/2022/02/fitts-law-touch-era/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>css</category>
      <category>ux</category>
    </item>
  </channel>
</rss>
