Showing posts with label web design. Show all posts
Showing posts with label web design. Show all posts

Tuesday, November 6, 2018

A Journey of One Thousand Miles: Different styles and uses of progress indicators

One feature, common to nearly every process (and especially most processes in ecommerce), is something that indicates progress - a map, if you will, that shows your current location relative to the destination. Even our Instant Pot has a display that indicates when pressure builds and releases. But, just as every process is different, there should be different techniques that indicate the process and the current state.

One of the most common uses of progress indicators is in multi-page forms - that's certainly where I've had the most exposure to them. In general, they're intended to reduce cognitive load in a process, and they can either succeed or fail, often with significant results.

In the Web Accessibility Initiative section for multi-page forms, several different methods for identifying progress are put forward. The methods used to indicate progress for multi-page forms can be briefly described as (a) landmark content, (b) a progressbar, and (c) a step-by-step indicator - and I'll take each of them in turn to discuss how they might be used and whether or not they can, or should, apply to other types of progress.

Landmark Content

The use of landmark content to identify progress is almost always a good method, especially when - as the first approach identified by the WAI - updating the title element is used. As a general rule, assistive technology will announce changes to the page title. The other approach in the landmark content method, updating the main heading - i.e. the h1 element - is also a good approach as many users navigate using headings and it is, or at least should be, more visible than the page title. Rather than use one of these approaches, use both.

Of course, there are disadvantages to using this method. Neither approach - updating the page title or updating the main heading - is likely to be sufficient if the user has scrolled far enough. Updates are likely to be noticed only by users who have the main heading in their field of vision or are using assistive technology that announces changes. Also, both approaches in this method update content, so not all accessibility is improved - the user still must either read, or have assistive technology that will read, the updated content.

A Progressbar

A progressbar generated using a progress element with a value of 1 and a max of 3, as rendered by Chrome
Progressbar
(from a progress element, rendered in Chrome)
HTML5 offers a progress element that takes a max attribute and a value attribute to draw a visual representation. The progress element takes content, which must be updated, as in <progress max="7" value="1">Step 1 of 7</progress>. However, in some platforms, a progress bar is animated in a way that would violate the Web Content Authoring Guidelines Success Criterion 2.2.2, an A-level criterion.

Like several of the additions in HTML5, the progress element is not very accessible, so it's still recommended a better widget - one that uses the progressbar role with aria-valuemin, aria-valuemax, and aria-valuenow - be used, but be advised that automatic updates to the value in a progressbar role are not well-defined.

The progressbar, whether implemented using the HTML5 progress element or a widget that uses the progressbar role, may be sufficient if the progress reflected is proportional, such as a file transfer showing the number of bytes transferred. The interface is not well-suited to processes where one or more steps is larger, or takes more time, than others.

A Step-by-step Indicator

An ordered list as a step-by-step indicator
Step-by-step Indicator
The third method - a step-by-step indicator - can help users orient themselves in multiple ways. First, the user should be able to clearly see how much progress they've made, whether the progress within each segment is proportional or not. Second, the content should reflect not only steps already completed but the current step, and not-started, or upcoming, steps. In this method, there are three common approaches we can use, and each will apply to different cases.

Fixed-Journey Indicator

The basic step-by-step indicator presented for multi-page forms by the WAI is an ordered list, with list items that have visually hidden content to indicate which item is completed or current. This is likely sufficient, if the progress is a consecutive series of unidirectional steps under the control of the user - although the WAI example should be updated to reflect capabilities available in the ARIA states and properties.

Breadcrumbs

If, progress is bidirectional or the user may complete steps in a non-consecutive manner, the interface should be a navigational one which allows the user to choose the step they'd like to complete. This step-by-step indicator becomes more complex as it not only needs to include completed, current, and not-started states but also accessible navigation between the steps. This sort of navigational, step-by-step indicator is often called a breadcrumb because it's more than just a progress indicator.

This type of progress indicator should especially be used for multi-page forms if those forms cause legal commitments or financial transactions to occur or update user-provided data that has been stored to help meet WCAG Success Criterion 3.3.4 as it aids in the review, confirmation, and correction of information.

Status Indicator

The third type of step-by-step indicator is a status indicator. The status indicator is still, semantically, an ordered list, but a status may switch in a non-consecutive manner and the status is likely to be outside the control of the user. An example of this might be a payment transaction that moves from pending to authorizing to completed or a document retrieval that moves from requesting to receiving to received to loaded. In either of these cases, an intermediate step may be skipped, e.g., the payment may appear to move from pending to completed or the document may move from receiving to loaded.

This type of progress indicator is perhaps the most complex, because the user interface is not really a progress indicator but a status indicator (hence the name). The upcoming statuses need not be announced because their inclusion is likely to reduce cognitive accessibility - the user can do nothing to either prevent or encourage the move to that state. Further, announcing which of the items in the list is current lacks the context that the visual interface has, so identifying completed and current items is a little more complex.

It is also important to note that because this indicator is auto-updating and representing something outside the control of the user, it is critical that it only be used for those activities that are essential. If the interface is used for a part of an activity that is non-essential, the auto-update feature without the ability to pause, stop, or hide the update will violate WCAG Success Criterion 2.2.2, just as the HTML5 progress element would.

Conclusion

Any of the four different types of progress indicators - a progressbar, a fixed-journey indicator, breadcrumbs, or a status indicator - can be sufficient to describe process. While they have areas of overlap, each has a specific interface that leads to a design pattern and accessibility features.

In the very near future, I will be launching a gitbook called Think A11y that will be covering components like this, including HTML, CSS, and JavaScript, in an effort to put my accessibility resources in one location. This blog will still cover accessibility issues from time to time, but hopefully this new approach will make my electronic life a little easier to manage and share. In the meantime...

Happy coding.

Saturday, June 30, 2018

Why you should never code for new browsers

A quick, very short post today.

First, I should preface this by saying that I've been writing human/computer interfaces for a very long time...and for a good part of that time - it's been more than two decades now, I've been writing for these crazy user-agents we call browsers. The Mosaic Wars are a little fuzzy, but I remember the First Browser War vividly.

Every great once in a while, I see a question along the lines of "is x compatible with some-really-old-browser" and there's always a response along the lines of "no one uses that browser anymore, if you're coding for anything older than n - 1, then you're wasting money".

I can say with 100 percent certainty, that if you're coding for specific browsers - be they old or new - then you're wasting money.

Browsers are released very frequently - for Chrome and Firefox, it's typically about every 6 weeks. The specs they claim to support are release much less frequently...for example, a new HTML spec was release every 1 or 2 years between 1995 and 1999 when HTML 4.01 came out, but the next spec - HTML5 - didn't come out for 14 years. CSS first came out in 1996 and was updated about every 2 years until CSS 3 came out (in 1999). 

What's that I hear? A host of voices singing the popular refrain "but we use JavaScript"? JavaScript is finally to the point where it is currently being updated annually - which makes it the fastest changing language of front-end development and still much slower than 6 to 8 times a year.

Add into this mix of rapidly updating browsers the mix of assistive technology (with its own specifications), and we begin to get a picture of extremely volatile user-agents and very stable specifications.

If there was one thing we should have learned from the First Browser War, it's that we should be not be coding according features that are available in one browser that we then try to port to another browser with extra work. Determine what your needs (and wants) are, code to the specification, and let the browsers catch up. Browsers update much faster and are very likely to render your code as you want it to look within the year without additional work from you. Also, this approach is as backwardly-compatible as possible (what in the accessibility world we call being robust)...and if you're not attempting to deliver a product to every end user, regardless of the user-agent they use, then you really are wasting money.

Happy coding.

Saturday, May 26, 2018

Finding Your Way

One of the common challenges when writing interfaces is describing processes that have multiple steps in a way that is both understandable and accessible. Adding different functionality and different devices further complicates this. If we were Hänsel or Grethel, we would just leave a trail of pebbles or breadcrumbs.

Alas, we often find that even though we are not Hänsel or Grethel, navigation within a process is still often a problem and poorly written HTML or CSS can significantly contribute to the several common accessibility concerns and usability concerns surrounding the progress/breadcrumbs component as well. This post offers a pattern that is accessible and mobile friendly without being a burden.

Again, the HTML and CSS needed to provide a usable breadcrumbs component is relatively simple, but it can pose an even greater problem if it is not written correctly. It should also be noted that in addition to the pattern described here, it is very important that the page title and the primary heading should be modified to indicate the update progress.

When first building our progress bar or breadcrumbs, we must make the determination if our it represents unidirectional progress or if we might be able to restart the process. If the flow is unidirectional, you might consider using the progress element to demonstrate progress; however, be aware that the progress element is dependent on the OS in use, and the ability to modify the display is limited.

If we are providing the ability to move backward in the process - i.e., the process is not unidirectional - that means the breadcrumbs or progress bar has a navigational element, and it should be identified as such. The good news is that much of the code is unchanged. To demonstrate the difference in the code, both versions are provided below.

Image showing breadcrumbs with the current step highlighted, in both mobile and desktop.
Breadcrumbs Snapshot
The code below, is complete, with the exception that in the demonstration there is a style rule to center the content, and it produces the breadcrumbs show in the Breadcrumbs Snapshot.



HTML (without navigation)

<div class="breadcrumbs">   <ol >     <li>       <span data-step="#1" role="text">         <span class="description">Personal Information</span>       </span>     </li>     <li aria-current="step">       <span data-step="#2" role="text">         <span class="description">Financial Information</span>       </span>     </li>     <li>       <span data-step="#3" role="text">         <span class="description">Review Information</span>       </span>     </li>     <li>       <span data-step="#4" role="text">         <span class="description">Thank You</span>       </span>     </li>   </ol> </div>



HTML (with navigation)

<nav aria-label="Breadcrumb" class="breadcrumb">   <ol>     <li>       <a href="signup/1">       <span data-step="#1" role="text">         <span class="description">Personal Information</span>       </span>       </a>     </li>     <li aria-current="step">       <a href="signup/2">       <span data-step="#2" role="text">         <span class="description">Financial Information</span>       </span>       </a>     </li>     <li>       <a href="signup/3">       <span data-step="#3" role="text">         <span class="description">Review Information</span>       </span>       </a>     </li>     <li>       <a href="thankyou">       <span data-step="#4" role="text">         <span class="description">Thank You</span>       </span>       </a>     </li>   </ol> </nav>

A few items to note - one is the inclusion of the aria-current attribute, which lets users of assistive technology know which step is current, and another is the inclusion of the role attribute, which fixes a 'feature' in some assistive technologies that sees child elements as distinct from their wrapping elements, resulting in a different value for the accessible name than what would commonly be expected. Otherwise, the code should be self-explanatory.

Next, we add some CSS



CSS

.breadcrumbs {   display: inline-block;   width: auto; } .breadcrumbs ol {   list-style-type: none;   margin: 0 auto;   overflow: visible;   padding: 0; } .breadcrumbs ol li {   float: left;   background: darkgray;   border: 0.1em solid darkgray;   color: white;   padding: 0.5rem;   position: relative;   margin-right: 1rem; } .breadcrumbs ol li::after {   background: darkgray;   content: '';   display: inline-block;   height: 0.5rem;   left: 100%;   margin: 0;   padding: 0;   position: absolute;   top: 0.7rem;   /* width = margin-right + (border-width * 2) */   width: 1.2em;   z-index: -1; } .breadcrumbs ol li:last-of-type {   margin-right: 0; } .breadcrumbs ol li:last-of-type::after {   display: none; } .breadcrumbs ol li a {   background: inherit;   color: inherit;   text-decoration: none; } .breadcrumbs ol li[aria-current] {   background: green;   border-color: green;   color: white; } .breadcrumbs ol li[aria-current] ~ li {   background: lightgray;   color: black; } @media (max-width: 20rem) {   .breadcrumbs ol li {     border-radius: 100%;   }   .breadcrumbs ol li [data-step]::before {     content: attr(data-step);   }   .breadcrumbs ol li a .description {     clip: rect(0, 0, 0, 0);     clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);   position: absolute;   } }

Here again, we need to comment about the code. First, the .breadcrumbs ol li rule is for past steps. This allows us to use the aria-current attribute as the selector for current steps and siblings of aria-current after the aria-current item - .breadcrumbs ol li[aria-current] ~ li - are those steps not yet completed. Another issue to note is that we are using the float property on list items. We might have used the display property to modify where items display, but in many assistive technologies, modification of the display property modifies the accessibility tree, rendering all our effort to construct an accurate tree moot.


Admittedly, this is a simple example; however, it should suffice to demonstrate how a progress bar or breadcrumbs might be built.

Happy coding.

Saturday, January 20, 2018

Getting the light show started

Anyone who's followed me for very long knows that one of my big concerns is accessibility. Today I'm going to tell you exactly how you can violate accessibility rules and possibly trigger epileptic seizures in a portion of the population at the same time. I'm sharing this information because this should NEVER happen, and yet it does (this means I've seen it in the wild).

Let's take, as a hypothetical case, a design where when you hover over something you want to show a callout - a very common design. In browser-based terms, there are two ways you can handle hovering interactions - using the CSS pseudoclass (which you should only use with focus and active, by the way) and with JavaScript using the mouse events.

Using CSS it's relatively straightforward - add the two elements (we'll call them the target and callout) to the DOM as siblings, placing the callout after the target, and hide the callout by default and use a sibling selector with the hover pseudoclass to show the callout. By the way, this shifts the show/hide functionality to the GPU and keeps it out of the script stack - which means it'll provide better performance. This is the best way to implement this little trick.

Unfortunately, more than a few engineers in our industry don't like CSS and prefer to do everything in JavaScript. In this case, we would still have the two DOM nodes and we would add event listeners to show the callout when we mouseover the target and hide the callout when we mouseover it. This method keeps the show/hide in the script stack, meaning it'll likely get janky at some point.

Here's where the bad stuff comes into play.

If the target and callout overlap (they way they sometimes will - especially in a responsive design), they will 'flash' as the 'hover' events fire on each element. This will be bad, very bad, especially if there is significant contrast between the colors, unless you're trying to simulate a nightclub or light show and don't really care about the likelihood of triggering photosensitive epilepsy.

If you want to see how this works, run the code samples below in a browser, but be warned, the target/callout flashes more than three times per second.

CSS
<!DOCTYPE html> <html> <body> <style type="text/css"> #callout, #target {   border: 1px solid black;   height: 10rem;   left: 20px;   position: absolute;   top: 100px;   width: 90%; } #callout {   z-index: -1; } #target:hover + #callout {   z-index: 10; } </style> <div id="parent">   <p id="target" style="background-color: #0f0;">target</p>   <p id="callout" style="background-color: #f00;">callout</p> </div> </body> </html>


JavaScript
<!DOCTYPE html> <html> <body> <style type="text/css"> #callout, #target {   border: 1px solid black;   height: 10rem;   left: 20px;   position: absolute;   top: 100px;   width: 90%; } #callout {   z-index: -1; } </style>
<div id="parent"> <p id="target" style="background-color: #0f0;">target</p> <p id="callout" style="background-color: #f00;">callout</p> </div>
<script> var callout = document.getElementById('callout'); var target = document.getElementById('target'); target.addEventListener('mouseover', function() {     callout.style.zIndex = '10'; }); callout.addEventListener('mouseover', function() {     callout.style.zIndex = '-1'; }); </script> </body> </html>


Notes

  1. A callout is those little bubbles that have a triangular-ish thing connecting the bubble to an item as a visual context clue.
  2. The events mouseenter, mouseleave, mousemove, mouseout,  and mouseover are the events applicable to a 'hover'

Tuesday, December 5, 2017

Print It!

Several years ago, Smashing Magazine did an article called 5 Powerful Tips and Tricks for Print Style Sheets, but we don't often remember print stylesheets today because, let's be honest, people don't generally print things anymore - at most, we save them to PDF on our device - but it is still an important part of the design of the interface.

Print stylesheets, however, can be pretty powerful, and there are a lot of times we still want a hardcopy of something we've worked on. In the financial services sector, we see this fairly often. People want a record of what they've signed up for - especially when it comes to paying interest. It doesn't really matter if the "hardcopy" is a PDF or a tree-killing version you can hold in your hands, we want it to look official.

There are a few things you'll want to keep in mind when printing, but generally, read the Smashing Magazine article I've linked above. One issue the Smashing Magazine article doesn't go into detail about is font size. Don't mess about with the font size. There are loads of posts about accessibility and font size and how to use rem or em rather than px on a web page - the same holds true for printed versions. You don't want someone getting the small print version when what they see on the screen is large print.

One thing that deserves a special callout - the Smashing Magazine article talks about expanding links and this is very important. In printed versions the user will not (generally) have the ability to hover over or follow a link, so make sure the appropriate links are followed by the URL in plain text. I'd suggest making certain the following is in your print stylesheet.



CSS

a:not([href^=javascript]):after {   content: " <" attr(href) "> "; } a[href="#"]:after {   content: "" !important; }


I'm going to go a little further here, because if you wish to exclude other links, let's say anything linked to "foo.com", for example, simply add those to the first rule, and use the in-string regular expression operator, like so...


CSS

a:not([href*=foo\.com]):not([href^=javascript]):after {   content: " <" attr(href) "> "; } a[href="#"]:after {   content: "" !imporant; }


...and you've included the link, enclosed in < and >, in your printed versions.

You'll notice, that on line one there are two differences in the attribute selector. First, I'm using a different regular expression selector, *= and second, I have a \ before the "." in "foo.com".

The selector *= matches a partial value in an attribute (you can learn more about regular expressions in attribute selectors at https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors), so take care that you have the most complete partial possible - in my example, links to barfoo.com would also be excluded.

Additionally, the . is a special character in CSS syntax (as are ":" and "/"), which means it will need to be escaped in order to be seen as part of the value, just as "http://www.foo.com" would need to be escaped as "http\:\/\/www\.foo\.com".

That's all there is to it - powerful selectors and the function that returns an attribute can make your printed (and saved-to-PDF) documents better for your users.

Happy coding.

Wednesday, September 20, 2017

True colors

Today's post is one that I'd generally qualify as 'quick and dirty', because it gets straight to the point and is mostly code with a little thinking thrown on top, like the cream on cup of hot chocolate (mmm, now I'm thinking about a nice cup of cocoa from Butler's and a stroll along High Street to the River Corrib and the Spanish Arch).

Group of colored pencils
One of the most troublesome accessibility issues is luminance contrast. Simply put, when setting the background and foreground color (background-color and color in CSS), you must maintain sufficient contrast. Most of the time, we have leave this task to designers and simply implement the vision they specify, including the various colors used; however, we as developers bear a responsibility for the interface as well, and that includes making certain that it is usable by as many people as possible.

To help us make certain a page is usable by as many people as possible, we have standards that include luminance contrast set out in the WCAG (by the way, if you're still following other standards, please join us in the 21st century). I won't bother to go into great detail about why they use the calculations they do to determine the luminance value of a color - other than the human eye perceives the different colors projected by a monitor (red, green, and blue) as having different luminance and that the luminance can be calculated. The discussion of the different equations that could be used to calculate luminance and how the human eye perceives luminance on a monitor is entirely tangential to this post, however. For our current discussion, I have put the equations used in the WCAG into JavaScript code that you can reuse.

Using the functions below you can build CSS preprocessor mixins that will automatically adjust colors based on their starting point, adjusting by 1 percent each iteration. Pay special attention to the limits set for large text and normal text (in the code as LIMIT_TEXT_LARGE and LIMIT_TEXT_NORMAL) because it make a significant difference in the colors available when moving from AA to AAA compliance. The function as defined below uses the values for AAA compliance rather than the less strict AA rules. For your convenience, the values for AA compliance are documented in the code if you wish to use those.

JavaScript
/**  * @private  * @description Normalizes a color value and returns an object with red, green, and blue values  * @returns {object}  * @param {string|object} value  */ function color(value) {   this.r = 0;   this.g = 0;   this.b = 0;   /**    * @description Returns a version darker by the specified percentage    * @returns {object}    * @param {number} amt    */   this.darken = function(amt) {     var delta = [this.r, this.g, this.b].map(function(c, i) {       var adjust = Math.min(Math.round((0 - c) * amt), -1);       return Math.max(adjust + c, 0);     });     this.r = delta[0];     this.g = delta[1];     this.b = delta[2];     return this;   };   /**    * @description Returns the hex value of the color    * @returns {string}    */   this.hex = function() {     return '#' +       ('0' + this.r.toString(16)).substr(-2) +       ('0' + this.g.toString(16)).substr(-2) +       ('0' + this.b.toString(16)).substr(-2);   };   /**    * @description Calculates luminance according to the WCAG. Lower values indicate darker color.    * @returns {number}    * @see https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests    */   this.luminance = function() {     var colors = [this.r, this.g, this.b].map(function(c) {       c /= 255;       return (c < 0.03929) ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);     }),     lum = (0.2126 * colors[0]) + (0.7152 * colors[1]) + (0.0722 * colors[2]);     return lum;   };   /**    * @description Returns a version lighter by the specified percentage    * @returns {object}    * @param {number} amt    */   this.lighten = function(amt) {     var delta = [this.r, this.g, this.b].map(function(c, i) {       var adjust = Math.max(Math.round((255 - c) * amt), 1);       return Math.min(adjust + c, 255);     });     this.r = delta[0];     this.g = delta[1];     this.b = delta[2];     return this;   };   /**    * @private    * @description constructs an object using the specified color    */   var h3 = /\b([0-9a-f])([0-9a-f])([0-9a-f])\b/i,     h6 = /\b([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\b/i;   if (typeof value === 'string' && (h3.test(value) || h6.test(value))) {     h3 = h3.exec(value);     h6 = h6.exec(value);     this.r = parseInt(h6 ? h6[1] : h3[1]+h3[1], 16);     this.g = parseInt(h6 ? h6[2] : h3[2]+h3[2], 16);     this.b = parseInt(h6 ? h6[3] : h3[3]+h3[3], 16);   } else {     this.r = parseInt(value.r || '0', 16);     this.g = parseInt(value.g || '0', 16);     this.b = parseInt(value.b || '0', 16);   }   this.r = isNaN(this.r) ? 0 : this.r;   this.g = isNaN(this.g) ? 0 : this.g;   this.b = isNaN(this.b) ? 0 : this.b;   return this; } /**  * @description Calculates the luminance contrast and adjusts colors when applicable  * @returns {object}  * @param {string|object} fg - foreground in hexadecimal or an object with red, green, blue values  * @param {string|object} bg - background in hexadecimal or an object with red, green, blue values  * @param {string|number} fs - font size  * @param {boolean} bf - font is bold-face  */ function colorize(fg, bg, fs, bf) {   /**    * @private    * @description Compares two colors and returns true if the contrast is 7:1    * @returns {boolean}    * @param {object} color1    * @param {object} color2    * @param {boolean} large    */   function lowContrast(color1, color2, large) {     var LIMIT_TEXT_LARGE = 4.5, // for AAA, use 3.1 for AA       LIMIT_TEXT_NORMAL = 7; // for AAA, use 4.5 for AA     var lum1 = color1.luminance(),       lum2 = color2.luminance(),       diff = (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05);     if (isNaN(lum1) || isNaN(lum2)) {       throw new TypeError('Unable to calculate luminance for colors');     }     if (diff >= LIMIT_TEXT_LARGE) {       return (diff < LIMIT_TEXT_NORMAL && !large);     }     return true;   }   /* large print is defined as at least 14pt, which is approximately 1.2rem or 19 px */   var background = new color(bg),     foreground = new color(fg),     pt = /(\d{1,})pt/.exec(fs),     em = /(\d{1,})r?em/.exec(fs),     px = /(\d{1,})px/.exec(fs),     lp = ((px && ((parseFloat(px[1]) > 18 && bf) || parseFloat(px[1]) > 24)) ||       (pt && ((parseFloat(pt[1]) > 14 && bf) || parseFloat(pt[1]) > 18)) ||       (em && ((parseFloat(em[1]) > 1.1 && bf) || parseFloat(em[1]) > 1.5))) ||       false;   if (background.luminance() < foreground.luminance()) {     /* while the contrast is low, darken the darker color and lighten the lighter color */     while (lowContrast(background, foreground, lp)) {       background.darken(0.01);       foreground.lighten(0.01);     }   } else {     while (lowContrast(background, foreground, lp)) {       background.lighten(0.01);       foreground.darken(0.01);     }   }   return {     background: background,     foreground: foreground   }; }


Happy coding.

Wednesday, May 31, 2017

Unnecessary Complexity: A case against ReactJs

I'll admit, even though the title of this post might imply otherwise, my experience with ReactJs is limited. Unlike a lot of UI engineers, I have been working primarily in pure HTML, CSS, and JavaScript since I began more than two decades ago. Oh, sure I've used popular JavaScript “libraries” in the past – like YUI – and I've written more than a few over the years for some pretty big companies. I've also used some pretty popular “frameworks” – like BackboneJs – and combined them with other JavaScript libraries (e.g., NodeJs, Express, and DustJs). Overall, though, even though I had no prima facie opinion of ReactJs, I've avoided it – in much the same way that I've avoided winning the lottery – but all that has changed with the current workscape as more and more companies adopt ReactJs.

I should mention that I'm generally not a fan of any websites or applications built without using Progressive Enhancement, but then if you've read much of my writing you already know that, so the subtitle – A case against ReactJs – is a little misleading as this isn't just a case against ReactJs but against a practice of which the use of ReactJs is just an example.

I also must point out that I'm not a fan of the WSOD that results from many JavaScript-driven pages. While that's more a general issue with client-side frameworks and how they're woven into a front-end architecture, it also applies to ReactJs. I'm also not a fan of loads of JavaScript that is dependency heavy, intercepts DOM events, encourages a development process that isn't progressive, or discourages graceful degradation.

So, why single out ReactJs when it's clearly not the only library to do this? Good question. It's popular. Massively popular. From the number of job descriptions including it as either a requirement or “nice-to-have”, it's pretty easy to see that without knowledge of or experience with this particular library (it's not a framework), it's becoming very difficult to even get past the CV screening phase.

Although ReactJs is not the only example of client-side libraries that dot the Interwebz landscape, as one of (arguably) the most popular libraries, it bears close examination. And although ReactJs isn't the only example of what's wrong with UI engineering – there are plenty of other examples – most of them boil down to the willingness of engineers to sacrifice the user experience in an effort to make their job easier.
The more layers are piled into increasingly complex systems, the more failure paths we introduce. We’ve learned that automation does not eliminate errors. Rather, it changes the nature of the errors that are made, and it makes possible new kinds of errors. Capt. Chesley B. “Sully” Sullenberger

I hear you, and yes, they do say that it's a poor craftsman that blames his tools, which means all this flak I'm directing toward ReactJs might be misplaced. Am I not just blaming a tool for poorly-written code? There are two distinct lines of response that I would take. First, saying that it's a poor craftsman that blames his tools does not imply that the tools used are unimportant. No craftsman would wield a dull blade that made rough cuts when fine cuts were the goal. Every craftsman knows that other famous tool-related expression that clearly says when the only tool you have is a hammer that everything looks like a nail. There is a tool appropriate to every job. Second, I would posit that libraries and frameworks – things like ReactJs – are not, in fact, tools.

If we look at the artisan analogy, the tools in that case are HTML, CSS, and JavaScript. Libraries like ReactJs, and even frameworks like AngularJs, are not really an artisan's tools – they're not the base ingredients that make up a dish, the closer analogy is that they're the prepared foods other artisans have made. As prepared foods, they make the kitchen's job easier, but at a cost, because they remain in the end product even after it's gone to market. They're the frozen, processed food of the Interwebz, encouraging people working in the kitchens to masquerade as Beard Award winners.

Taking this foodie analogy further, as each new processed food is typically built upon other processed foods, the list of ingredients (dependencies) grows longer as “more layers are piled into increasingly complex systems”. These increasingly complex systems (dishes) are not only more fragile but also fraught with other issues, such as increased payload size (which is an issue for anyone connecting over a data-limited network, like mobile) and performance issues as all downloaded code executes in the browser. In the end, users end up with a bloated mess, but at least the “engineers” got the code out in time. They are literally, as the saying goes, “getting shit done”...and just as we wouldn't call someone working in a kitchen combining prepared foods into what must only loosely be described as a “dish” a “chef”, we shouldn't call those who create the monstrosities only loosely termed a “user interface” an “engineer”.

We must, as a community, get back to building actual user interfaces. We must, as a community, stop the madness. We must, as a community, become engineers again. Get the HTML out of your JavaScript. Get the CSS out of your JavaScript. Build agnostic, slim interfaces that everyone can use. We must, as a community, because we are the only ones who can.

Tuesday, February 14, 2017

Oh, the places you'll go

One of the things that took the most effort building the eBay checkout interface for PayPal was making sure everything appeared correctly no matter in which area you lived. To show how different this is, let's look at a few differences between my two favorite countries - the United States and Ireland.

Let's look at three simple, obvious examples first...like an address


The Lord Mayor of Dublin The President of the United States
Mansion House
Dawson Street
Dublin 2
D02 AF30
Ireland
The White House
1600 Pennsylvania Ave NW
Washington, DC 20500
United States

If we think of this as a microformat, both have a street-address - Dawson Street or 1600 Pennsylvania Ave NW - and both have a locality (city) - Dublin or Washington - and both have a country...and they even (now) both have a postal-code. The number after the city name in the Lord Mayor's residence works something like a postal code, but it's not - and it's also not the locality, it's a region within the city. Additionally, the Ireland address also has no region...and as with many street addresses in Ireland, there is no building number. Nearly every country has its own addressing scheme - and yes, there are addresses written in a format similar to the US format - but there are often differences and they seldom end with the physical address.

Telephone numbers are displayed differently. For example, the Lord Mayor's telephone number is currently shown on the website as 01 222 6200 (which leaves off the country code, though to be fair I'm sure it's seldom that anyone calling the Lord Mayor from outside Ireland would not know to dial 353 beforehand) and on the website for The White House the switchboard is listed as 202-456-1414 (again leaving off the country code). The minor difference here is that in the US there is nearly always a clear delineation between the parts of a telephone number - e.g., the area code is first and is typically either followed by a hyphen or a space and sometimes it is wrapped in parentheses and sometimes it's missing altogether. The central office portion (that's the 456 bit in the number for The White House) is followed by a hyphen but sometimes a dot - I don't recall the last time I saw it connected directly to the line (e.g., 4561414) or the last time I saw it followed by a space.

Finally, let's look at dates. I don't mean those tasty bits of chewy goodness that grow on tall skinny trees, I mean days of the year. In the US you'll nearly always see them collected and displayed as month followed by a slash, then date followed by a slash, then year - for example, 12/31/1999 for New Year's Eve in 1999. The US is nearly the only country where you'll see this month-day-year format. In most other countries, including Ireland, you're much more likely to see day-month-year (which you'll also see if you're looking at a US government document, but in that case you're likely to see that same date as 31-DEC-99).

Why does all this matter?

First there are the little miscommunications - like when the British Prime Minister was scheduled to visit The White House and several individuals were not admitted because the dates they provided as birth dates didn't match the date security personnel had.

Second, there are bigger issues - for example goods and money can be misrouted, negatively affecting any number of things such as someone's life or livelihood.

For people focused, in even a minor way, on developing software people have to actually use, there are two points of impact. First, having to adjust for myriad tiny differences increases the cognitive load - which is a significant contributor to conversion. Second, the amount of time spent localizing an application can be significant, because not only do interaction points - like date collection inputs - have to be localized, designs have to accommodate longer words without breaking.

My experience in working with both Fortune 500 corporations and small consulting firms has shown me one consistent truth - US firms focus on developing for the US market first. Granted, the US is a big market, but imagine if you were an auto manufacturer and you learned that every car you produced had to be customized for the customer. It's not that every customer wants a different color, it's that one customer wants four doors, another two, the next wants five, and yet another wants one and he wants it to open vertically behind the motor so he can climb over the steering column. You'd likely say that's nuts - we'll build a car with four doors and everyone will use the same car. There's a legend that Henry Ford said something like that - you could have any color Ford motorcar as long as it's black - and he did get away with it, for a time. In these days of being globally aware, your organization is not likely to have the same option. If you want to sell widgets in China or Mexico or Germany, you'll have to appeal to the Chinese, Mexican, or German market - or at least not make them hate buying your widgets until they can find a new source.

If you are going to launch in a global market - any global market - build your software from the perspective that you will be internationalizing it. Build a common interface, but build it in such a way that it reduces cognitive load and has the best experience for the greatest number of people. In the long run, you'll be glad you did...and when the next customer wants zero doors and handlebars in place of a steering wheel, you'll be able to say "here's your motorcylce" in weeks rather than months.

Happy coding.

Thursday, December 22, 2016

Watching the Children

One of the things I've been working on is an AngularJS component that groups elements into a single field...which I thought would be simple. As it turned out, I learned more than I thought I would.

First, let me describe the scenario. The original block of code, that came out of a collaboration between designers and front-end developers, was inadequate. Here's what the code was (sort of) like...


Original Angular Markup

<div class="row">   <label>{{fieldLabel}}</label>   <select name="{{id}}_day" ng-model="day">     <option ng-repeat="option in ::days track by ::$index">       {{option}}     </option>   </select>   <select name="{{id}_month" ng-model="month">     <option ng-repeat="option in ::months track by ::$index">       {{option}}     </option>   </select>   <select name="{{id}}_year" ng-model="year">     <option ng-repeat="option in ::years track by ::$index">       {{option}}     </option>   </select>   <tooltip ></tooltip> </div>


Looking at the code I immediately knew that this was not written by someone who was familiar with several concepts every engineer must keep in mind - remember, we are getting paid to think - things like flexibility for localization, and more importantly, accessibility.

My first lesson

My first thought (beyond the obvious lack of accessibility practice, like having a linked label) was "why are they not using an input with a date type attribute". When I did some accessibility testing of my own, however, I learned that the user experience with an HTML5 date type input was far below what I would consider sub-par. Sure, the HTML5 date type has a better visual experience, but if you're relying on something like VoiceOver™ it's very unclear how you're even supposed to interact with the input. If you're relying on VoiceOver™, you'd have a much better experience if the input format (e.g., year/month/day or year-month-day) were described and you were left to enter it in a text type input.

Granted, from an accessibility standpoint there is little we could do that would not improve the experience. I was, however, limited in options - I had to retain the dropdown lists with the only label associated with the group as the primary (visual) interface - so I started where I could. After the first pass - to address the accessibility issues - the code was a little better, even if I did have to add a method to the scope object to set the date value using the three date parts. (Ok, I didn't have to add that, I could still pass the date parts, but adding it and gives me a single point of reference for the date, making validation and other stuff a little easier.)


Accessible Angular Markup

<div class="row">   <label for="{{id}}">{{fieldLabel}}</label>   <input class="visually-hidden" id="{{id}}" name="{{id}}" type="text">   <div aria-hidden="true">     <select name="{{id}}_day"      ng-change="setDate()"      ng-model="day">       <option ng-repeat="option in ::days track by ::$index">         {{option}}       </option>     </select>     <select name="{{id}_month"      ng-change="setDate()"      ng-model="month">       <option ng-repeat="option in ::months track by ::$index">         {{option}}       </option>     </select>     <select name="{{id}}_year"      ng-change="setDate()"      ng-model="year">       <option ng-repeat="option in ::years track by ::$index">         {{option}}       </option>     </select>   </div>   <tooltip ></tooltip> </div>


Although I won't delve into the tooltip directive here, I also made sure it had the ARIA role "status" so when the date was invalid (like a Date of Birth less than 13 years ago for Facebook), I could put that in the tooltip and it would announce to anyone using assistive technology as well as being displayed for anyone.

My second lesson

With the majority of the accessibility issues fixed, I moved on to improving the interface in other ways. There was a significant problem in that the order of the SELECT elements was fixed even though the day-month-year pattern applies to only a few cultures. The directive, as it was coded, would not be flexible enough to handle different formats - different ordering of the date parts - that are common across the globe.

Also, to improve the interface, it would be nice if the number of days were automatically adjusted based on the month and year. Not only does it reduce the cognitive load for poorly labelled elements, it reduces the number of accidental invalid dates like February 30th. Since Angular has two-way binding, and a way to watch properties in a scope for changes, this little modification should have been fairly simple. The good news is that it was. All I had to do was add $watch to the month in the directive and modify the days...oh, and $watch the year as well to account for February's 29th day in leap years.

With the ability to watch the year and month for changes and because JavaScript already has pretty slick slicing and dicing methods for arrays, modifying the number of days was easy peasy, and using days as an array on the scope also meant that passing that array into the ng-options attribute, made perfect sense.

Since I wanted to be able to pass the months array in (to ease localization) this was the perfect time to make that little switch and add a date format to the scope that enabled me to switch around the order of the dropdown lists. A little parsing of the date format that was passed in and I have an order property on the scope that I can use for another of Angular's features - ng-switch.

After these little changes, I had something that looked like this...


Localizable Angular Markup

<div class="row">   <label for="{{id}}">{{fieldLabel}}</label>   <input class="visually-hidden" id="{{id}}" name="{{id}}" type="text">   <div aria-hidden="true">     <ng-switch on="::order[0]">       <div class="datepart" ng-switch-when="d">         <ng-include src="'dd.html'"></ng-include>       </div>       <div class="datepart" ng-switch-when="m">         <ng-include src="'mm.html'"></ng-include>       </div>       <div class="datepart" ng-switch-when="y">         <ng-include src="'yy.html'"></ng-include>       </div>     </ng-switch>     <ng-switch on="::order[1]">       <div class="datepart" ng-switch-when="d">         <ng-include src="'dd.html'"></ng-include>       </div>       <div class="datepart" ng-switch-when="m">         <ng-include src="'mm.html'"></ng-include>       </div>       <div class="datepart" ng-switch-when="y">         <ng-include src="'yy.html'"></ng-include>       </div>     </ng-switch>     <ng-switch on="::order[2]">       <div class="datepart" ng-switch-when="d">         <ng-include src="'dd.html'"></ng-include>       </div>       <div class="datepart" ng-switch-when="m">         <ng-include src="'mm.html'"></ng-include>       </div>       <div class="datepart" ng-switch-when="y">         <ng-include src="'yy.html'"></ng-include>       </div>     </ng-switch>   </div>   <tooltip ></tooltip> </div>


Included Markup Files - dd.html

<select name="{{id}}_day"   ng-change="setDate()"   ng-model="day"   ng-options="days.indexOf(day) as day for day in days"> </select>


Included Markup Files - mm.html

<select name="{{id}_month"   ng-change="setDate()"   ng-model="month"   ng-options="months.indexOf(month) as month for month in months"> </select>


Included Markup Files - yy.html

<select name="{{id}}_year"   ng-change="setDate()"   ng-model="year"   ng-options="years.indexOf(year) as year for year in years"> </select>


Using this ng-switch pattern with parsing code similar to...


Format Parsing

var format = (/^([ymd]*)\W?([ymd]*)\W?([ymd]*)/i)        .exec($scope.dateformat).slice(1, 4); $scope.order = [   format[0].replace(/y+/i, 'y').replace(/m+/i, 'm').replace(/d+/, 'd'),   format[1].replace(/y+/i, 'y').replace(/m+/i, 'm').replace(/d+/, 'd'),   format[2].replace(/y+/i, 'y').replace(/m+/i, 'm').replace(/d+/, 'd') ];


...in the directive means that even a two-part date format, like month/year, will be displayed correctly, and you can use the same datepart identifiers as are used in Angular's date filter.

Third lesson

So now it was worlds better - accessible and ready for localization. That tooltip, though, was going to give me fits. Getting it to display when the input had focus and hide when it didn't was easy...but the SELECT elements are not siblings of the tooltip (preventing CSS from working) and the focus event doesn't bubble so I couldn't add a listener to the element containing the SELECTs that was a sibling to the tooltip. To make matters worse, in Angular, the focus event doesn't fire for a SELECT element until the change event fires. It was like some grand puzzle preventing me from getting the tooltip to hide and show when the focus was on any one of the date parts.

So, how did I solve this little riddle?

First, I thought to use $watch on the SELECT elements in some way. Of course the first problem in attempting that is that by using the ng-switch to help with localization I had prevented the SELECT elements from being present in a way that would allow me to get a hook into them. Add to this the fact that the focus event wasn't firing and I thought I was blocked...but then I remembered I was getting paid to think.

I added the onfocus and onblur attributes to the SELECT items in the markup to by-pass Angular. That fixed the timing problem - the onfocus and onblur fire immediately when focus or blur occur, not when the change event occurs, and I thought I could use them to change the class to indicate focus and use $watch in the directive to watch the class attribute of the SELECT. Unfortunately, that didn't work. Even though I was able to get the class to change by adding onfocus="this.className+='focused'" and onblur="this.className=this.className.replace(/\bfocused\b/, '')" to the SELECT tags, the element instance in the DOM was out of sync with the element instance in the directive. I knew the onfocus and onblur event handlers were key, though, because they allowed me to get the focus event without the change event.

Given that I had to figure out how to get the two references in sync, what I needed was a way to get the onfocus and onblur attributes to reference the directive scope. Referencing the directive scope wasn't enough, however, because I also had to be able to call $digest to get any changes to the scope made outside of Angular (which native event handlers are) recognized. To make that sync easier, I added methods to the scope - one to set a scope property (focused) to true, and one to set the same property to false - and this is key - made sure those methods also called $digest. Next, I changed onfocus and onblur to call the methods I had added to the directive scope in the SELECT elements, and then added the ng-class attribute to the DIV containing the SELECT elements to identify that the field had focus any time the property was true (i.e., ng-class="{'focused': focused}").


Included Markup Files - dd.html

<select name="{{id}}_day"   onblur="angular.element(this).scope().blur();"   onfocus="angular.element(this).scope().focus();"   ng-change="setDate()"   ng-model="day"   ng-options="days.indexOf(day) as day for day in days"> </select>

So, at this point, I was pretty sure it was all set...and, just like in the LEGO movie, everything is awesome.

So, there you go...a quick way to watch the children of a DOM element, sort of.

Happy coding.

Monday, June 6, 2016

Scrolling, Scrolling

When you're short on real estate on your web page, one of the easiest things to do is shorten the height of an element and make it scrollable. This works especially well with a table, but if you're not careful, the header of the table will scroll along with all the content, making the column headers invisible and rendering the table more difficult to use. Fortunately, fixing this problem is relatively easy - as you'll see below.

First, you really should start with good, clean, semantic markup. That means a THEAD tag and a TBODY tag. So we'll start with something like the following...

HTML
<table class="scrollable">   <caption>10 of the world's busiest airports</caption>   <thead class="thead">     <tr>       <th>Country</th><th>City</th><th>Activity</th><th>Name</th><th>Latitude</th><th>Longitude</th>     </tr>   </thead>   <tbody>     <tr><td>US</td><td>ATL</td><td>68343</td><td>Hartsfield Jackson Atlanta International</td><td>33.636719</td><td>-84.428067</td></tr>     <tr><td>US</td><td>ORD</td><td>59692</td><td>Chicago O'Hare International</td><td>41.978603</td><td>-87.904842</td></tr>     <tr><td>US</td><td>DFW</td><td>56496</td><td>Dallas Fort Worth International</td><td>32.896828</td><td>-97.037997</td></tr>     <tr><td>US</td><td>LAX</td><td>51396</td><td>Los Angeles International</td><td>33.942536</td><td>-118.408075</td></tr>     <tr><td>CN</td><td>PEK</td><td>48226</td><td>Capital International</td><td>40.080111</td><td>116.584556</td></tr>     <tr><td>US</td><td>CLT</td><td>44583</td><td>Charlotte Douglas International</td><td>35.214</td><td>-80.943139</td></tr>     <tr><td>US</td><td>DEN</td><td>44438</td><td>Denver International</td><td>39.861656</td><td>-104.673178</td></tr>     <tr><td>US</td><td>LAS</td><td>41164</td><td>McCarran International</td><td>36.080056</td><td>-115.15225</td></tr>     <tr><td>US</td><td>IAH</td><td>39808</td><td>George Bush Intercontinental</td><td>29.984433</td><td>-95.341442</td></tr>     <tr><td>GB</td><td>LHR</td><td>37680</td><td>London Heathrow</td><td>51.4775</td><td>-0.461389</td></tr>   </tbody> </table>


Next, we need to adjust the CSS to override the default styles.

First, make sure that you have at least some specificity, otherwise all your tables, even those that take up very little vertical space will be affected. You'll notice from line 1 of our HTML that we're doing this by adding scrollable as a class on the table.

Second, we're adjusting both the TBODY and the TR tags to override the default. We're going to add a rule for overflow (on the y-axis) on the TBODY and for the height. A note about the height - the default line-height for a table row is 1.5 em, so if you don't adjust the line-height, using a multiple of 1.5 will give you a full line at the bottom - assuming you don't have borders around the TR or TD. In the example, I'm using a 6em height, which shows 4 full lines.

One might think that setting the height and overflow-y on the TBODY is enough, but alas it is not. The display mode is still table, which overrides the height specification. To correct this, I'll change the display to block. Unfortunately, that has the sad side-effect of rendering the cells within the table using an auto width. I'll correct this - in part by setting the display on the TR to table - and in part by setting the width on each of the columns. This gives us the CSS below.

CSS
table.scrollable > tbody {   display:block;   height:6em;   overflow-y:scroll; } table.scrollable > thead, table.scrollable > tbody tr {   display:table; } table.scrollable th:nth-child(1), table.scrollable td:nth-child(1) {   width:5.5em; } table.scrollable th:nth-child(2), table.scrollable td:nth-child(2) {   width:4em; } table.scrollable th:nth-child(3), table.scrollable td:nth-child(3) {   width:5.5em; } table.scrollable th:nth-child(4), table.scrollable td:nth-child(4) {   width:17em; } table.scrollable th:nth-child(5), table.scrollable td:nth-child(5) {   width:5.5em; } table.scrollable th:nth-child(6), table.scrollable td:nth-child(6) {   width:6.5em; }

If you would rather have evenly-spaced columns, you can set the width on the TR within the TBODY to 100% and change the table-layout to fixed, e.g., change line 8 in the CSS to display:table; table-layout:fixed; width:100%; and remove lines setting width on all columns.

That's it. Your table is now scrollable with a stationary header.
10 of the world's busiest airports
CountryCityActivityNameLatitudeLongitude
USATL68343Hartsfield Jackson Atlanta International33.636719-84.428067
USORD59692Chicago O'Hare International41.978603-87.904842
USDFW56496Dallas Fort Worth International32.896828-97.037997
USLAX51396Los Angeles International33.942536-118.408075
CNPEK48226Capital International40.080111116.584556
USCLT44583Charlotte Douglas International35.214-80.943139
USDEN44438Denver International39.861656-104.673178
USLAS41164McCarran International36.080056-115.15225
USIAH39808George Bush Intercontinental29.984433-95.341442
GBLHR37680London Heathrow51.4775-0.461389


Happy coding.

Friday, May 27, 2016

Testing? We don't need no stinkin' testing!

Today's post is twofold. Let me start with a story.

I was filling out an application online for a service, and the provider wanted my address. Although I'm unclear as to why it was required, it seemed reasonable enough. I was given a simple form with space for a street address, extended address, locality, region, and postal code - all easy enough. As I filled in the street address, the address verification started. As I continued typing, the address verification service displayed matching addresses in a drop-down list directly below the street address. When my address appeared in the list, I clicked it...and a message displayed that the address could not be retrieved. "OK", I thought, "I'll just add the locality, region, and postal code on my own"...but those fields had been disabled by JavaScript.

First, let me offer a few observations about the interface. To begin, I'm giving a +5 bonus on the Craft: Webpage check to the engineers who built the form because they tried to use Progressive Enhancement. If they hadn't, I would likely have been hopelessly stuck. As it was, I was able to turn off JavaScript (I highly recommend the Toggle JavaScript plugin for Chrome, by the way) and complete the form. Next, I'm given a -10 bonus on the Craft: Webpage check for requiring JavaScript to submit the form. Seriously?! Just make the submit button a submit button.

Also, I get why they wanted to turn off the Chrome address autofill - if there's a bad address in the cache it will get propagated to their system of record - but that assumes the person using the computer is less trustworthy than the system validating the address, and that's not always a good thing. Yes, users make mistakes, but that's why we do things like label our inputs and use skeumorphic (or otherwise familiar) design patterns. I would even go so far as to say that if your users are making a lot of "mistakes", like pulling on door handles when they should be pushing, your design needs to change...and there are a lot of examples of everyday things that are poorly designed, just take a look at some of the examples at baddesign.com, so you're not alone.

Addresses, however, are a special challenge. Why? Well, first because if you're doing anything internationally there are several different formats for addresses (luckily for you, http://prototypes.cathmhaol.com/hcard/ has most of them identified by the areas that use them) - there is a significant degree of ambiguity to address. Second, there has been a validation issue for a long time, which is why so many services have popped up to validate both foreign and domestic addresses. This is especially critical for online properties, because delivery is an issue that someone coming into a brick and mortar store does not face. In short, the technical and functional skills required to build a well-performing address component are not insignificant.

As I said earlier, this post is twofold. First, there are issues surrounding the address entry experience in my story. Yes, many of them were eased by the use of Progressive Enhancement - and that is a very important lesson here, because - and here's the second part - stuff breaks. We even have a saying that "complex systems break in complex ways"...and I can tell you based on my experience in the tech industry that it's not a matter of "if" but "when" and it will most likely be at the worst possible time. It will be in the last step of a multi-part process that makes customers so angry that not only will they not return, they'll tell 10 other people about their bad experience and soon you'll have a crowd of people who "know a guy".

So, let's look at what it would have taken to improve this experience - it's actually a pretty minor improvement. You'll notice from the story that there were two responses from the service. One was a response that included a matching address (that I clicked) and one was a response that the address could not be retrieved. Using simple logic we know that the user identified a verified address (so we don't really have to worry about a 'wrong' address) and that there was some sort of error during retrieval. Had the failure (error) case on the retrieval action enabled the fields that were disabled earlier, my work around would not have been necessary.

In order to have the best user experience you're going to want things like validation, autofills, animation, bells, whistles, and even the machine that goes "ping"...but all that complexity comes at a price...and that price is enhanced fault tolerance, error handling, and recovery...and testing...lots and lots of testing. All of that especially comes into play in data validation. In this specific case the engineers didn't need to worry so much about fault tolerance (we know the address is valid), but the error handling could use work. In cases where the fault tolerance or error handling is incomplete, or the testing of those two features is incomplete, you're often better off not having the functionality. Never assume something won't happen, because it will...and because assumptions are not a good thing (Robert's Rule #12). Consider this - "corner cases" and "outliers" exist often enough that we've named them...and if they've earned a name, they've earned our attention.

Now, go give the engineer who tests your code some pizza...or a donut...or a beer - something that says you appreciate them and how often they've saved you by identifying a corner case you missed.

Happy coding.