{"version":"https://jsonfeed.org/version/1","title":"Aaron Gustafson: Content tagged user experience","description":"The latest 20 posts and links tagged user experience.","home_page_url":"https://www.aaron-gustafson.com","feed_url":"https://www.aaron-gustafson.com/feeds/user-experience.json","author":{"name":"Aaron Gustafson","url":"https://www.aaron-gustafson.com"},"icon":"https://www.aaron-gustafson.com/i/og-logo.png","favicon":"https://www.aaron-gustafson.com/favicon.png","expired": false,"items":[{"id":"https://www.aaron-gustafson.com/notebook/links/the-ux-of-html/","title":"đź”— The UX of HTML","content_html":"
Somehow my students are allergic to semantics and shit. And they’re not alone. If you look at 99% of all websites in the wild, everybody who worked on them seems to be allergic to semantics and shit. On most websites heading levels are just random numbers, loosely based on font-size. Form fields have no labels. Links and buttons are divs. It’s really pretty bad. So it’s not just my students, the whole industry doesn’t understand semantics and shit.
I feel this… deeply. And I 100% agree with where Vasilis is coming from here. I do take a bit of umbrage with the idea that heading levels don’t matter—they really do—but his point about getting folks excited about the stuff they get for free by paying attention to their markup is something I’ve been pushing for years as well.
If you’re interested in a related deep dive into HTML’s lack of dependencies, check out this piece I wrote for Smashing Magazine. If you’d like to dive deeper into forms, I have this talk you might like.
","url":"https://www.aaron-gustafson.com/notebook/links/the-ux-of-html/","external_url":"https://www.htmhell.dev/adventcalendar/2023/1/","tags":["progressive enhancement","user experience","HTML"],"image":"https://www.htmhell.dev/images/og/advent23_1.png?s=011221s","date_published":"2024-01-05T22:52:50Z"},{"id":"https://www.aaron-gustafson.com/notebook/sharing-in-the-age-of-3p-cookie-mageddon/","title":"✍🏻 Sharing in the Age of 3p Cookie-mageddon","summary":"Over a decade ago, I wrote up detailed instructions on how to enable users to share your content on social media without allowing them to be tracked by every social media site via cookies. In a few short weeks “third party” cookies will get the boot in Chromium-based browsers. If you’re still relying on third party share widgets on your site, now is a good time to replace them. Here’s how…","content_html":"Over a decade ago, I wrote up detailed instructions on how to enable users to share your content on social media without allowing them to be tracked by every social media site via cookies. In a few short weeks “third party” cookies will get the boot in Chromium-based browsers. If you’re still relying on third party share widgets on your site, your users may start seeing problems. Now is a good time to replace them with code that Just Works™. Here’s how…
When it comes to sharing, there are myriad ways to do it. If you’re at all familiar with my work, it should come as no surprise that I always start with a universally-useable and accessible baseline and then progressively enhance things from there. Thankfully, every social media site I commonly use (with the exception of the Fediverse) makes this pretty easy by providing a form that accepts inbound content via the query string.1 You can try LinkedIn’s to see it in action.
Each service is a little different, but all function similarly. I support the following ones in this site:
Site | Destination | URL | Optional Params |
---|---|---|---|
Twitter / X | https://twitter.com/ | url | |
Hacker News | https://news.ycombinator.com/ | u | t = the title you want to share |
https://www.facebook.com/ | u | ||
https://www.linkedin.com/cws/share | url | ||
https://pinterest.com/ | url | media = an image to sharedescription = the text you want to share |
Using this information, I created a partial template for use on any page in this site (though I mainly use it on blog posts right now). Each link includes useful text content (e.g., “Share on ______”) and a local SVG of the service’s icon. Here’s a simplified overview of the markup I use:
<ul class=\"social-links social-links--share\">
<li class=\"social-links__item\">
<a href=\"{{SHARE URL}}\" class=\"social-link\" rel=\"nofollow\">
<svg>{{SERVICE ICON}}</svg>
<b class=\"social-link__text\">Share on{{SERVICE NAME}}</b>
</a>
</li>
</ul>
You can check out the baseline experience on this very page by disabling JavaScript.
It’s worth noting that I have chosen not to enforce opening these links in a new tab. You can do that if you like, but on mobile devices I’d prefer the user just navigate to the share page directly. You may have a different preference, but if you decide to spawn a new tab, be sure your link text lets folks know that’s what will happen. I do include a rel="nofollow"
on the link, however, to prevent search spiders from indexing the share forms.
If you test out these links, you’ll notice many of the target forms will pick up a ton of information from your page automatically. By and large, this info is grabbed from your page’s Open Graph data (stored in meta
tags) or Linked Data (as JSON-LD). You can write that info to your page by hand or use a plugin to generate it for you automatically. There are a ton of options out there if you choose to go the later route (which I’d recommend).
If you played around with any of the various share forms, you probably noticed that they are, by and large, designed as discrete interactions best-suited to a narrow window (e.g., mobile) or popup. To provide that experience, I’ve long-relied on a little bit of JavaScript to launch them in a new, appropriately-sized window:
function popup( e ) {
var $link = e.target;
while ( $link.nodeName.toLowerCase() != \"a\" ) {
$link = $link.parentNode;
}
e.preventDefault();
var popup = window.open( $link.href, 'share',
'height=500,width=600,status=no,toolbar=no,popup'
);
try {
popup.focus();
e.preventDefault();
} catch(e) {}
}
var screen_width = \"visualViewport\" in window ?
window.visualViewport.width : window.innerWidth,
$links = document.querySelectorAll(
'.social-links--share a'
),
count = $links.length;
if ( screen_width > 600 ) {
while ( count-- ) {
$links[count].addEventListener('click', popup, false);
$links[count].querySelector(
'.social-link__text'
).innerHTML += \" (in a popup)\";
}
}
The first chunk defines a new function called popup()
that will act as the event listener. It takes the event (e) as an argument and then finds the associated link (bubbling up through the DOM as necessary in that while
loop). Once it finds the link, the function opens a new popup window (using window.open()
). Then, to check if the popup was blocked, it attempts (within the try…catch
) to focus it. If the focus succeeds, which means the popup wasn’t blocked, the script prevents the link from navigating the user to the href
(which is the default behavior, hence e.preventDefault()
).
The second block defines a couple of variables we’ll need. First, it captures the current screen_width using either the window’s visualViewport
(if available) or its innerWidth
(which is more old school). Next it grabs the social links ($links) and counts them for looping purposes (count).
The final block is a conditional that checks to see if the screen_width is wider than 600px (an arbitrary width that just feels right… your mileage may vary). If the screen is wider than that threshold, it loops through the links,2 adds the click handler, and adds some text to the link label to let folks know it will open a popup.
And with that, the first layer of enhancement is complete: Users with JavaScript support who also happen to be using a wider browser window will get the popup share form if the popup is allowed. If the popup isn’t allowed, they’ll default to the baseline experience.
A few years back, browsers began participating in OS-level share activities. On one side, this allowed websites to share some data—URLs, text, files—to other apps on the device via navigator.share()
. On the other side of the equation, Progressive Web Apps could advertise themselves—via the Manifest’s share_target
member—as being able to receive content shared in this way.
Sharing a URL and text is really well supported. That said, it’s only been around a few years at this point and some browsers require an additional permission to use the API.3 For these reasons, it’s best to use the API as a progressive enhancement. Thankfully, it’s easy to test for support:
if ( \"share\" in navigator ) {
// all good!
}
For my particular implementation, I’ve decided to swap out the individual links for a single button that, when clicked, will proffer the page’s details over to the OS’s share widget. Here’s the code I use to do that:
var $links = document.querySelector('.social-links--share'),
$parent = $links.parentNode,
$button = document.createElement('button'),
title = document.querySelector('h1.p-name,title').innerText,
$description = document.querySelector(
'meta[name=\"og:description\"],meta[name=\"description\"]'
),
text = $description ? $description.getAttribute('content')
: '',
url = window.location.href;
$button.innerHTML = 'Share <svg>…</svg>';
$button.addEventListener('click', function(e){
navigator.share({ title, text, url });
});
$parent.insertBefore($button, $links);
$links.remove();
The first block sets up my variables:
ul
) of sharing links;h1
or title
element);meta
description element;The second block sets up the button by inserting the text “Share” and an SVG share icon and setting an event listener on it that will pass the collected info to navigator.share()
.
The third and final block swaps out the link list for the button.
The final step to putting this all together involves setting up the conditional that determines which enhancement is offered. To keep everything a bit cleaner, I’m also moving each of the enhancements into its own function:
!(function(window, document, navigator){
function prepForPopup() {
// popup code
}
function popup() {
// popup event handler
}
function swapForShareAPI() {
// share button code
}
if (\"share\" in navigator ) {
swapForShareAPI();
} else {
prepForPopup();
}
})(this, this.document, this.navigator);
With this setup in place, I can provide the optimal experience in browsers that support the web share API and a pretty decent fallback experience to browsers that don’t. And if none of these enhancements can be applied, users can still share my content to the places I’ve identified… no cookies or third-party widgets required.
You can see (and play with) an isolated demo of this interface over on Codepen.
Interesting side-note: If you own a form like this on your site, it makes a great share target. ↩︎
Why a reverse while
loop? Well, the order of execution doesn’t matter and decrementing while
loops are faster in some instances. It’s a micro-optimization that boosts perf on older browsers and lower-end chipsets. ↩︎
Like many modern APIs, it also requires a secure connection (HTTPS). ↩︎
It was a long time coming, but I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. I’m pretty excited about it!
I had the original idea for “projections” way back in 2019. Inspired by OS X’s Dashboard Widgets and Adobe AIR, I’d begun to wonder if it might be possible to project a component from a website into those kinds of surfaces. Rather than building a bespoke widget that connected to an API, I thought it made sense to leverage an installed PWA to manage those “projections.” I shared the idea at TPAC that year and got some interest from a broad range of folks, but didn’t have much time to work on the details until a few years later.
In the intervening time, I kept working through the concept in my head. I mean in an ideal world, the widget would just be a responsive web page, right? But if that were the case, what happens when every widget loads the entirety of React to render their stock ticker? That seemed like a performance nightmare.
In my gut, I felt like the right way to build things would be to have a standard library of widget templates and to enable devs to flow data into them via a Service Worker. Alex Russell suggested I model the APIs on how Notifications are handled (since they serve a similar function) and I was off to the races.
I drafted a substantial proposal for my vision of how PWA widgets should work. Key aspects included:
iframe
);After continuing to gently push on this idea with colleagues across Microsoft (and beyond), I discovered that the Windows 11 team was looking to open up the new Widget Dashboard to third-party applications. I saw this as an opportunity to turn my idea into a reality. After working my way into the conversation, I made a solid case for why PWAs needed to be a part of that story and… it worked! (It no doubt helped that companies including Meta, Twitter, and Hulu were all invested in PWA as a means of delivering apps for Windows.)
While the timeline for implementation didn’t allow us to tackle the entirety of my proposal, we did carve out the pieces that made for a compelling MVP. This allowed us to show what’s possible, see how folks use it, and plan for future investment in the space.
Sadly, it meant tabling two features I really loved:
I’m sincerely hopeful these two features eventually make their way to us as I think they truly unlock the power of the widget platform. Perhaps, with enough uptake on the current implementation, we can revisit these in the not-too-distant future.
To test things out, I decided to build two widgets for this site:
Both are largely the same in terms of their setup: They display a list of linked titles from this site.
Given that they were going to be largely identical, I made a single “feed” template for use in both widgets. The templating tech I used is called Adaptive Cards, which is what Windows 11 uses for rendering.
Adaptive Card templates are relatively straightforward JSON:
{
\"type\": \"AdaptiveCard\",
\"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",
\"version\": \"1.6\",
\"body\": [
{
\"$data\": \"${take(items,5)}\",
\"type\": \"Container\",
\"items\": [
{
\"type\": \"TextBlock\",
\"text\": \"[${title}](${url})\",
\"wrap\": true,
\"weight\": \"Bolder\",
\"spacing\": \"Padding\",
\"height\": \"stretch\"
}
],
\"height\": \"stretch\"
}
],
\"backgroundImage\": {
\"url\": \"https://www.aaron-gustafson.com/i/background-logo.png\",
\"verticalAlignment\": \"Bottom\",
\"horizontalAlignment\": \"Center\"
}
}
What this structure does is:
items
from the data being fed into the template (more on that in a moment);item
andtitle
and url
keys from the item
object)The way Adaptive Cards work is that they flow JSON data into a template and render that. The variable names in the template map directly to the incoming data structure, so are totally up to you to define. As these particular widgets are feed-driven and this site already supports JSONFeed, I set up the widgets to flow the appropriate feed into each and used the keys that were already there. For reference, here’s a sample JSONFeed item
:
{
\"id\": \"…\",
\"title\": \"…\",
\"summary\": \"…\",
\"content_html\": \"…\",
\"url\": \"…\",
\"tags\": [ ],
\"date_published\": \"…\"
}
If you want to tinker with Adaptive Cards and make your own, you can do so with their Designer tool.
With a basic template created, the next step was to set up the two widgets in my Manifest. As they both function largely the same, I’ll just focus on the definition for one of them.
First off, defining widgets in the Manifest is done via the widgets
member, which is an array (much like icons
and shortcuts
). Each widget is represented as an object in that array. Here is the definition for the “latest posts” widget:
{
\"name\": \"Latest Posts\",
\"short_name\": \"Posts\",
\"tag\": \"feed-posts\",
\"description\": \"The latest posts from Aaron Gustafson’s blog\",
\"template\": \"feed\",
\"ms_ac_template\": \"/w/feed.ac.json\",
\"data\": \"/feeds/latest-posts.json\",
\"type\": \"application/json\",
\"auth\": false,
\"update\": 21600,
\"icons\": [
{
\"src\": \"/i/icons/webicon-rss.png\",
\"type\": \"image/png\",
\"sizes\": \"120x120\"
}
],
\"screenshots\": [
{
\"src\": \"/i/screenshots/widget-posts.png\",
\"sizes\": \"387x387\",
\"label\": \"The latest posts widget\"
}
]
}
Breaking this down:
name
and short_name
act much like these keys in the root of the Manifest as well as in shortcuts
: The name
value is used as the name for the widget unless there’s not enough room, in which case short_name
is used.tag
as analogous to class
in HTML sense. It’s a way of labeling a widget so you can easily reference it later. Each widget instance will have a unique id created by the widget service, but that instance (or all instances, if the widget supports multiple instances) can be accessed via the tag
. But more on that later.description
key is used for marketing the widget within a host OS or digital storefront. It should accurately (and briefly) describe what the widget does.template
key is not currently used in the Windows 11 implementation but refers to the expected standard library widget template provided by the system. As a template library is not currently available, the ms_ac_template
value is used to provide a URL to get the custom Adaptive Card (hence “ac”) template. The “ms_” prefix is there because it’s expected that this would be a Microsoft-proprietary property. It follows the guidance for extending the Manifest.data
and type
keys define the path to the data that should be fed into the template for rendering by the widget host and the MIME of the data format it’s in. The Windows 11 implementation currently only accepts JSON data, but the design of widgets is set up to allow for this to eventually extend to other standardized formats like RSS, iCal, vCard, and such.update
is an optional configuration member allowing you to set how often you’d like the widget to update, in seconds. Developers currently need to add the logic for implementing this into their Service Worker, but this setup allows the configuration to remain independent of the JavaScript code, making it easier to maintain.icons
and screenshots
allow us to define how the widget shows up in the widget host and how it is promoted for install.When someone installs my site as a PWA, the information about the available widgets gets ingested by the browser. The browser then determines, based on the provided values and its knowledge of the available widget service(s) on the device, which widgets should be offered. On Windows 11, this information is routed into the AppXManifest that governs how apps are represented in Windows. The Windows 11 widget service can then read in the details about the available widgets and offer them for users to install.
As I mentioned earlier, all of the plumbing for widgets is done within a Service Worker and is modeled on the Notifications API. I’m not going to exhaustively detail how it all works, but I’ll give you enough detail to get you started.
First off, widgets are exposed via the self.widgets
interface. Most importantly, this interface lets you access and update any instances of a widget connected to your PWA.
When a user chooses to install a widget, that emits a “widgetinstall” event in your Service Worker. You use that to kickoff the widget lifecycle by gathering the template and data needed to instantiate the widget:
self.addEventListener(\"widgetinstall\", event => {
console.log( `Installing ${event.widget.tag}` );
event.waitUntil(
initializeWidget( event.widget )
);
});
The event argument comes in with details of the specific widget being instantiated (as event.widget
). In the code above, you can see I’ve logged the widget’s tag
value to the console. I pass the widget information over to my initializeWidget()
function and it updates the widget with the latest data and, if necessary, sets up a Periodic Background Sync:
async function initializeWidget( widget ) {
await updateWidget( widget );
await registerPeriodicSync( widget );
return;
}
The code for my updateWidget()
function is as follows:
async function updateWidget( widget ) {
const template = await (
await fetch(
widget.definition.msAcTemplate
)
).text();
const data = await (
await fetch(
widget.definition.data
)
).text();
try {
await self.widgets.updateByTag(
widget.definition.tag,
{ template, data }
);
}
catch (e) {
console.log(
`Couldn’t update the widget ${tag}`,
e
);
}
return;
}
This function does the following:
self.widgets.updateByTag()
method to push the template and data to the widget service to update any widget instances connected to the widget’s tag
.As I mentioned, I also have code in place to take advantage of Periodic Background Sync if/when it’s available and the browser allows my site to do it:
async function registerPeriodicSync( widget )
{
let tag = widget.definition.tag;
if ( \"update\" in widget.definition ) {
registration.periodicSync.getTags()
.then( tags => {
// only one registration per tag
if ( ! tags.includes( tag ) ) {
periodicSync.register( tag, {
minInterval: widget.definition.update
});
}
});
}
return;
}
This function also receives the widget details and:
definition
(from the Manifest) includes an update
member. If it has one, it…tag
value and a minimum interval equal to the update
requested.The update
member, as you may recall, is the frequency (in seconds) you’d ideally like the widget to be updated. In reality, you’re at the mercy of the browser as to when (or even if) your sync will run, but that’s totally cool as there are other ways to update widgets as well.1
When a user uninstalls a widget, your Service Worker will receive a “widgetuninstall” event. Much like the “widgetinstall” event, the argument contains details about that widget which you can use to clean up after yourself:
self.addEventListener(\"widgetuninstall\", event => {
console.log( `Uninstalling ${event.widget.tag}` );
event.waitUntil(
uninstallWidget( event.widget )
);
});
Your application may have different cleanup needs, but this is a great time to clean up any unneeded Periodic Sync registrations. Just be sure to check the length of the widget’s instances
array (widget.instances
) to make sure you’re dealing with the last instance of a given widget before you unregister the sync:
async function uninstallWidget( widget ) {
if ( widget.instances.length === 1
&& \"update\" in widget.definition ) {
await self.registration.periodicSync
.unregister( widget.definition.tag );
}
return;
}
Widget platforms may periodically freeze your widget(s) to save resources. For example, they may do this when widgets are not visible. To keep your widgets up to date, they will periodically issue a “widgetresume” event. If you’ve modeled your approach on the one I’ve outlined above, you can route this event right through to your updateWidget()
function:
self.addEventListener( \"widgetresume\", event => {
console.log( `Resuming ${event.widget.tag}` );
event.waitUntil(
updateWidget( event.widget )
);
});
While I don’t want to get too into the weeds here, I do want to mention that widgets can have predefined user actions as well. These actions result in “widget click” events being sent back to the Service Worker so you can respond to them:
self.addEventListener(\"widgetclick\", event => {
const widget = event.widget;
const action = event.action;
switch ( action ) {
// Custom Actions
case \"refresh\":
event.waitUntil(
updateWidget( widget )
);
break;
}
});
For a great example of how a widget can integrate actions, you should check out the demo PWAmp project. Their Service Worker widget code is worth a read.
With all of these pieces in place, I was excited to see my site showing up in the Widget Dashboard in Windows 11.
You can view the full source code on GitHub:
I’m quite hopeful this will be the first of many places PWA-driven widgets will appear. If you’s like to see them supported elsewhere, be sure to tell your browser and OS vendor(s) of choice. The more they hear from their user base that this feature is needed, the more likely we are to see it get implemented in more places.
In wiring this all up, I ran into a few current bugs I wanted to flag so you can avoid them:
icons
member won’t accept SVG images. This should eventually be fixed, but it was keeping my widgets from appearing as installable.screenshots
members can’t be incredibly large. I’m told you should provide square screenshots no larger than 500px ×500px.Have you checked out Server Events? ↩︎
Interesting examination of label positioning relative to checkboxes and radio controls in forms. While ostensibly web-focused, it applies equally to any GUI.
","url":"https://www.aaron-gustafson.com/notebook/links/assume-the-position-a-labelling-story/","external_url":"https://www.tpgi.com/assume-the-position-a-labelling-story/","tags":["accessibility","forms","design","user experience"],"image":"https://www.tpgi.com/wp-content/uploads/labelling-should-be.png","date_published":"2023-06-07T20:40:33Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/checkout-usability-autodetect-city-and-state-inputs-based-on-the-user-s-postal-code-28-of-mobile-sites-don-t-/","title":"🔗 Checkout Usability: Autodetect “City” and “State” Inputs Based on the User’s Postal Code (28% of Mobile Sites Don’t)","content_html":"Excellent overview of how to progressively enhance address entry forms using postal codes. It’s both a time saver and a data quality guard. Great stuff!
","url":"https://www.aaron-gustafson.com/notebook/links/checkout-usability-autodetect-city-and-state-inputs-based-on-the-user-s-postal-code-28-of-mobile-sites-don-t-/","external_url":"https://baymard.com/blog/zip-code-auto-detection?utm_medium=email&utm_campaign=Article%20Checkout%20Usability%20Autodetect%20City%20and%20State%20Inputs%20Based%20on%20the%20Users%20Postal%20Code%2028%20of%20Mobile%20Sites%20Dont&utm_content=Article%20Checkout%20Usability%20Autodetect%20City%20and%20State%20Inputs%20Based%20on%20the%20Users%20Postal%20Code%2028%20of%20Mobile%20Sites%20Dont+CID_06e39fd9939f69a009f7b4d63d1e0da8&utm_source=CampaignMonitor&utm_term=read%20the%20article","tags":["progressive enhancement","forms","user experience"],"image":"https://cdn.baymard.com/research/media_files/attachments/83684/original/research-media-file-37a02b5a04643939d772c093c9aeb1eb.jpg","date_published":"2023-04-12T23:01:09Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/how-ux-designers-can-engage-their-imagination/","title":"đź”— How UX designers can engage their imagination","content_html":"So much worthy of reflecting on on this piece!
I do not believe you can codesign your way to justice.
Certain institutions and design ideas are fundamentally oppressive, and the only way to achieve radical transformation at scale is with collective action and policy change.
Imagination is key, but imagination in the right way:
Imagination is not a splashy poster of a sci-fi movie but a daily act of resistance we must engage in despite how tired we might be.
Following this framing, author Alba Villamil walls through a ton of actionable above and examples of how to bring imagination to hear on our UX work. Well worth your time!
","url":"https://www.aaron-gustafson.com/notebook/links/how-ux-designers-can-engage-their-imagination/","external_url":"https://www.fastcompany.com/90846822/ux-designers-suffering-from-failure-of-imagination","tags":["user experience","inclusive design"],"image":"https://images.fastcompany.net/image/upload/w_1280,f_auto,q_auto,fl_lossy/wp-cms/uploads/2023/02/p-1-90846822-ux-ethics.jpg","date_published":"2023-02-18T20:11:12Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/digital-exclusion-in-healthcare-how-to-change-it/","title":"đź”— Digital Exclusion in Healthcare & How to Change It","summary":"Fantastic talk from Sareh on assumptions we make about our users and how those assumptions exclude people who have different lived experiences than we do.","content_html":"Fantastic talk from Sareh on assumptions we make about our users and how those assumptions exclude people who have different lived experiences than we do. Her focus is on digital healthcare, but is applicable to everything.
https://www.youtube.com/watch?v=Zi1NXGgsM3s
I love her calls to action as well!
Related talk: Delivering Critical Information & Services
","url":"https://www.aaron-gustafson.com/notebook/links/digital-exclusion-in-healthcare-how-to-change-it/","external_url":"https://www.youtube.com/watch?v=Zi1NXGgsM3s","tags":["accessibility","empathy","inclusive design","industry","user experience"],"date_published":"2023-01-27T17:17:39Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/disruptive-design-patterns-an-uncharted-territory/","title":"🔗 Disruptive design patterns — an uncharted territory","content_html":"Excellent advice here:
","url":"https://www.aaron-gustafson.com/notebook/links/disruptive-design-patterns-an-uncharted-territory/","external_url":"https://uxdesign.cc/disruptive-design-patterns-an-uncharted-territory-c1a857f2ff93","tags":["user experience"],"image":"https://miro.medium.com/max/1200/0*DqZGJjkCmJ9H9c8M","date_published":"2022-11-02T22:53:21Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/designing-better-error-messages-ux/","title":"🔗 Designing Better Error Messages UX","content_html":"[N]ext time you’re designing a new interface paradigm or chatting with an engineer, ask yourself about the risks involved in the known versus the unknown with the following questions.
- Does the new design use intuitive patterns that prioritize consistency?
- Are you in any way disregarding accessibility practices in favor of a feature or a visual direction?
- How tech-savvy are your users and can the newly-introduced experience be easily adopted by current and future, more-diverse audiences?
- Can and will your design decisions be validated through properly conducted user research and user testing?
Being mindful of these practices will help you guide decisions and ensure you don’t change things just because you can.
As you’d expect, Vitaly’s deep dive into error message UX is a treasure trove of excellent, practical advice to make data entry better for your customers.
","url":"https://www.aaron-gustafson.com/notebook/links/designing-better-error-messages-ux/","external_url":"https://www.smashingmagazine.com/2022/08/error-messages-ux-design/","tags":["forms","user experience","accessibility"],"image":"https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/93af7c7e-6484-4f47-9e79-fcf6aa24b3ce/better-error-messages-ux.jpg","date_published":"2022-08-30T16:56:41Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/why-everyone-is-watching-tv-with-closed-captioning-on-these-days/","title":"đź”— Why Everyone Is Watching TV with Closed Captioning On These Days","content_html":"Subtitles are yet another example of an accessibility feature that improves the experience of a much broader audience.
Personally, I started using closed captions when we came home with Oscar. It also me to watch movie & shows while he slept in my arms. Interestingly, I’ve always tuned in dialogue subtitles in video games.
","url":"https://www.aaron-gustafson.com/notebook/links/why-everyone-is-watching-tv-with-closed-captioning-on-these-days/","external_url":"https://kottke.org/19/04/why-everyone-is-watching-tv-with-closed-captioning-on-these-days","tags":["accessibility","user experience"],"image":"https://kottke.org/plus/misc/images/ice-law-order.jpg","date_published":"2019-04-27T14:43:51Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/policymakers-sound-alarm-on-dark-patterns-deceptive-web-design-tricks/","title":"🔗 Policymakers Sound Alarm On “Dark Patterns,” Deceptive Web Design Tricks","content_html":"The DETOUR Act, introduced by Sens. Mark Warner and Deb Fischer, targets bad actors on the web. I need to read through it fully to get a sense of what covered and/or missing, but that this is happening is, I think, a good thing.
","url":"https://www.aaron-gustafson.com/notebook/links/policymakers-sound-alarm-on-dark-patterns-deceptive-web-design-tricks/","external_url":"https://www.ndtv.com/world-news/policymakers-sound-alarm-on-dark-patterns-deceptive-web-design-trick-2020659","tags":["privacy","user experience"],"image":"https://c.ndtvimg.com/2019-04/4idbju0o_patterns650_625x300_10_April_19.jpg","date_published":"2019-04-10T15:18:25Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/best-practices-for-mobile-form-design/","title":"🔗 Best Practices For Mobile Form Design","content_html":"An exhaustive (and kinda overwhelming) look at best practices for form design in general. I wouldn’t even add “mobile” as an adjective. Good advice all around for accessibility and usability.
","url":"https://www.aaron-gustafson.com/notebook/links/best-practices-for-mobile-form-design/","external_url":"https://www.smashingmagazine.com/2018/08/best-practices-for-mobile-form-design/","tags":["forms","user experience","accessibility"],"image":"https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/1b028712-5289-4ae1-a57c-98a58f1d138e/1-best-practices-for-mobile-form-design-800w.jpg","date_published":"2018-08-31T21:59:23Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/time-to-modernize-government-websites/","title":"🔗 Reps. Khanna and Ratcliffe: It’s Time to Modernize Government Websites","content_html":"Hear, hear!
","url":"https://www.aaron-gustafson.com/notebook/links/time-to-modernize-government-websites/","external_url":"https://www.wired.com/story/time-to-modernize-government-websites/","tags":["society","user experience"],"image":"https://media.wired.com/photos/5b369eae440082328b06d023/191:100/w_1280,c_limit/fed_outdated-FINAL.jpg","date_published":"2018-07-03T23:36:58Z"},{"id":"https://www.aaron-gustafson.com/notebook/performance-as-user-experience/","title":"✍🏻 Performance as User Experience","summary":"Slides, notes, and resources from my Performance as User Experience talk at An Event Apart in Seattle.","content_html":"Government is supposed to work for the American people, and we owe it to them to do a better job. The tools we need to restore the United States’ global leadership in technology and digital government are already at our fingertips. Now it’s time to act.
Last Tuesday, I gave a relatively new talk on web performance and optimization at An Event Apart in Seattle. The response to this talk has been tremendous both times I gave it, so I wanted to share the deck and relevant links here.
This is a thoroughly exhaustive breakdown of notification patterns and how to make them inclusive. Great work Heydon!
","url":"https://www.aaron-gustafson.com/notebook/links/notifications/","external_url":"https://inclusive-components.design/notifications/","tags":["user experience","accessibility"],"image":"https://inclusive-components.design/assets/images/on-light.svg?v=62e4fa65bf","date_published":"2018-03-02T18:29:03Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/your-interactive-makes-me-sick/","title":"🔗 Your Interactive Makes Me Sick","content_html":"Eileen Webb on the accessibility issues created by “modern” storytelling on the web:
The issue usually isn’t the motion itself, or the existence of animation. The problem is a mismatch between my expectations for what I’m going to encounter on a webpage and what actually displays on that page.
She documents a handful of real issues and shows you how to resolve or at least mitigate them. She’s also included a bunch of real world examples of “dos” and “don’ts”. It’s well worth a read.
","url":"https://www.aaron-gustafson.com/notebook/links/your-interactive-makes-me-sick/","external_url":"https://source.opennews.org/articles/motion-sick/","tags":["user experience","accessibility","web design"],"image":"https://media.opennews.org/cache/69/1a/691a6ec11bc68314187a333a508a6f15.jpg","date_published":"2018-03-02T16:39:07Z"},{"id":"https://www.aaron-gustafson.com/notebook/web-form-conundrum-disabled-or-read-only/","title":"✍🏻 Web Form Conundrum: `disabled` or `readonly`?","summary":"It’s hard to know which is the right way to go, especially when presented with a choice between two seemingly similar options for disallowing a field to be edited.","content_html":"Web forms are complex beasts. There are a lot of field types to remember, each with dozens of attributes. It’s hard to know which is the right way to go, especially when presented with a choice between two seemingly similar options for disallowing a field to be edited: disabled
and readonly
.
TL;DR: If you really need it, which you probably don’t, readonly
is what you want.
There are times when you want to expose a bit of data to the user but don’t want them to be able to edit it. For example, your system might not allow a user to edit their username after completing the registration process. In situations like this, you may want to present the username in the context of a profile editing interface without allowing them to edit it.
The best choice in that situation would be to avoid using a form field to display the username, full stop, but if you’re hamstrung and need to drop it in an input
field, you want to make sure the user can’t edit it. That’s when you need to make a choice between disabled
and readonly
.
Both of these attributes are “empty” attributes, meaning they don’t require value assignment:
<label for=\"username-1\">Disabled Username</label>
<input id=\"username-1\" name=\"username-1\" disabled value=\"AaronGustafson\">
<label for=\"username-2\">Readonly Username</label>
<input id=\"username-2\" name=\"username-2\" readonly value=\"AaronGustafson\">
As expected, both also prohibit editing directly in the browser.
So why do we have two attributes that do the same thing? Unfortunately this is where developers often get confused: the user experience is the same, but the mechanics are quite different.
Fields marked as readonly
are collected along with all of the normal field values in a form submission (“successful controls” in the spec). The only difference between a readonly
field and a regular field is the user experience.
Fields marked as disabled
are ignored when collecting values from the form. In a traditional form submission, the action page would never receive values for a disabled
field, regardless of whether it has a name
attribute. In JavaScript, this is a little trickier as generic DOM access via a form’s elements
collection includes all form controls, including disabled
fields (and buttons, output
elements, etc.). In order to ensure consistency with the spec, it is incumbent upon the JavaScript developer to keep an eye out for disabled
fields so they can throw away their values before processing the form.
Thankfully, most library code I’ve found does this, so it’s not much of an issue if you are working with jQuery’s serialize()
method or even the form-serialize
module for Node (and React, etc.). Confusingly, the Node module enables developers to treat disabled
fields as though they are readonly
. Luckily, that’s not the default behavior.
In many of my forms-related talks, I’ve discussed the need for server side validation of info sent from the browser. Even if you have the most robust client-side validation logic in the world, that JavaScript (and your HTML, etc.) is all easily manipulated via common developer tools. If you don’t have equally robust checks running on the server side (be it via an API or simply a form-posting endpoint), you’re opening your system up for abuse.
It’s inconsequential to inspect a form field in the browser, remove the readonly
or disabled
attribute, and submit the form with a change to that field. If you, as the developer, truly don’t want the value of a particular key touched, don’t provide it in a field to begin with. Additionally, don’t accept any values submitted for it. You don’t need to throw an error to the user since it’s an improper use of the system, but you might consider logging it in case you see continued abuse by that user.
I want to display data as information, but don’t want a user to update it.
Don’t use a form field at all, display it as text.
I want the data included with the form submission.
Ideally, display the info as text (see above) and mix it into the form submission data on the server side. If that’s not possible, make it a readonly
field an ensure there’s a validation check on the server side.
I do not want the data included in the form submission.
Display the info as text (see above).
","url":"https://www.aaron-gustafson.com/notebook/web-form-conundrum-disabled-or-read-only/","tags":["forms","web design 101","HTML","user experience"],"date_published":"2017-10-31T16:07:39Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/flat-ui-less-attention-cause-uncertainty/","title":"🔗 Flat UI Elements Attract Less Attention and Cause Uncertainty","content_html":"Beware the “weak signifier”:
","url":"https://www.aaron-gustafson.com/notebook/links/flat-ui-less-attention-cause-uncertainty/","external_url":"https://www.nngroup.com/articles/flat-ui-less-attention-cause-uncertainty/","tags":["user experience","design"],"image":"https://media.nngroup.com/media/articles/opengraph_images/Flat_UI.png","date_published":"2017-09-12T16:08:12Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/remember-palms-webos-maybe-not-but-apple-and-google-definitely-do/","title":"🔗 Remember Palm’s WebOS? Maybe not, but Apple and Google definitely do","content_html":"When we compared average number of fixations and average amount of time people spent looking at each page, we found that:
- The average amount of time was significantly higher on the weak-signifier versions than the strong-signifier versions. On average participants spent 22% more time (i.e., slower task performance) looking at the pages with weak signifiers.
- The average number of fixations was significantly higher on the weak-signifier versions than the strong-signifier versions. On average, people had 25% more fixations on the pages with weak signifiers.
(Both findings were significant by a paired t-test with sites as the random factor, p < 0.05.)
This means that, when looking at a design with weak signifiers, users spent more time looking at the page, and they had to look at more elements on the page. Since this experiment used targeted findability tasks, more time and effort spent looking around the page are not good. These findings don’t mean that users were more “engaged” with the pages. Instead, they suggest that participants struggled to locate the element they wanted, or weren’t confident when they first saw it.
WebOS… so far ahead of its time:
And all of these features were available eight years ago!
","url":"https://www.aaron-gustafson.com/notebook/links/remember-palms-webos-maybe-not-but-apple-and-google-definitely-do/","external_url":"https://www.salon.com/2017/09/03/remember-palms-webos-maybe-not-but-apple-and-google-definitely-do/","tags":["user experience","mobile"],"image":"https://mediaproxy.salon.com/width/1200/https://media.salon.com/2017/09/pre.jpg","date_published":"2017-09-12T15:48:04Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/integrating-animation-into-a-design-system/","title":"đź”— Integrating Animation into a Design System","content_html":"An excellent (and exhaustive) look at how how animation can be integrated into style guides and pattern libraries. It includes excellent examples from FutureLearn, Google, Marvel, SalesForce.
Great work Alla!
","url":"https://www.aaron-gustafson.com/notebook/links/integrating-animation-into-a-design-system/","external_url":"https://alistapart.com/article/integrating-animation-into-a-design-system","tags":["user experience","animation"],"image":"https://i0.wp.com/alistapart.com/wp-content/uploads/2019/03/cropped-icon_navigation-laurel-512.jpg?fit=512%2C512&ssl=1","date_published":"2017-08-25T18:35:53Z"}]}