Author's note: I'm abandoning the day format as I realized I tend to work on things during the downtime of other moments, so the 'days' became arbitrary units of when I felt enough stuff was accomplished to write about, thus defeating the purpose entirely!
It's been quite a while! Towards the end of 2023, I got sucked into project work. I felt a bit mixed on this, I was grateful to not be struggling at a time when the industry faced mass layoffs, but at the same time, it really pulled me away from all the 'extracurricular' work I like to do. Nevertheless, 4 months later, I'm finally out of the woods and back on track.
At some point, I managed to get a deploy going on Netlify so I could be sure that the whole thing actually builds. I'm not exactly sure when this occurred, but I am very proud of my past self. I'd much rather debug a build as the process goes along, plus it gives me something to show people.
I suddenly remember that I want to switch over to CSS Logical Properties. I know I'm a little behind the curve on this one. I do often work on projects that involve Chinese, a language that can also be displayed vertically, so I want to try learn this ASAP. It's a pretty easy conversion, but I do get stuck on if I want to set widths and heights using logical properties. I'm not entirely sure I want to account for re-orienting this very complex window layout, so I just focus on the text properties for now, to treat this as a learning experience.
I build out the testimonials section in the style of a chat interface. After spending so long tinkering on little things, it's nice to do some CSS work where the little things fall into place! Note to self, go ask more recent clients for a testimonial.
I'm at a point where I'm starting to work on the styles for my long-form content. I spend way, way more time on this than I want to, but I am really picky about these things. It's all little things like tweaking the heading colors, the list styles, things like that. I love a good personal site where the text content just feels super polished and thought out.
I somehow find myself spending a significant amount of time working on the windows, again. There are slightly different behaviors for the windows based on things like its state (whether it's minimized, maximized, or in its default mode), the size of the viewport and whether or not there are other windows present on the screen. Plus, each window accepts a wide variety of positioning and sizing props, which still need to work responsively. Getting all those combinations right is gonna take up even more time than I thought. I make a mental note to check in with some accessibility friends about the expected focus management behavior.
I really want to make a table of contents. I know I'm VERY rambly and therefore it's nice to have a link to all the headings somewhere, but I realize that I need to build yet another window configuration if I'm to make it work. This variation on the window is positioned absolutely on middling screen size viewports, but centered on super large viewports. I get really sad that I can't use CSS variables in media queries, this would make this an extremely elegant way to handle this, but I end up just going with a number that works for now.
Shortly after finalizing this approach, I make a breakthrough on a grid-based approach that I had experimented with and abandoned prior. I was having trouble making sure the contents of my windows would scroll within their containers, but I realized I just had to keep it simple and remove the absolute positioning from the grid children. D'oh! I adjust the blog post and about pages to utilize this new layout, which looks nearly identical but it pleases me to have a more elegant solution that looks better on more screen sizes.
Here, I had another breakthrough and realized I could simplify my pixel borders. I was using this pixel borders SCSS mixin, and my major gripe with it was that it didn't work with CSS properties, due to being written in the SCSS age. The mixin also supported different levels of roundedness, and I had decided pretty early that I on that I only want a single 'pixel' chunk cut out of each corner. This means I could replace all of this:
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace +
str-replace(
str-slice($string, $index + str-length($search)),
$search,
$replace
);
}
@return $string;
}
@mixin pixel-borders(
$color,
$border-radius: calc(var(--border-thickness) * 3 + 1px)
) {
$escapedColor: str-replace($color + "", "#", "%23");
border-style: solid;
border-width: var(--border-thickness);
border-color: var(--border-color);
border-radius: $border-radius;
border-image-slice: 2;
border-image-width: 1;
border-image-outset: 0;
border-image-source: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='6' height='6'><path d='M0 2h2v2H0zM2 0h2v2H2zM4 2h2v2H4zM2 4h2v2H2z' fill='#{$escapedColor}' /></svg>");
}
With this:
@mixin pixel-borders() {
border: var(--border-thickness) solid var(--border-color);
mask: conic-gradient(
at var(--border-thickness) var(--border-thickness),
#000 270deg,
#0000 0
)
0 0 / calc(100% - var(--border-thickness))
calc(100% - var(--border-thickness));
}
I took much inspiration from this great article by Temani Afif. Thanks Temani, you really saved me on this one!
At this point, I contemplate moving off of SCSS mixins briefly. I am not against utility classes and have a couple myself, but I'm using mixins primarily for the pixel borders and focus outlines, and I may want them within blog post content. I don't particularly fancy applying a focus-outline
class to every single link in my text content, either through using a parser or manually. As a result, I conclude that mixins are helping me enough to survive this brutal round of code culling.
After doing a bit of cleanup, I come to the horrifying realization that CSS masks cover the outline property, which is needed for focus outlines. I read the spec to make sure this is as intended, and confirm that it is. It doesn't feel great to me since it means a wrapper element is necessary when using masks on interactive elements to make sure you don't completely break your outlines, but I accept this as a tradeoff.
With all this tinkering, I've probably spent an entire workday on changes that are basically completely invisible outside of a couple of niche cases:
It's hard not to feel a little despair here at the time to effort ratio. I take solace in the fact that during the process of sorting all this out, I did a deep dive into CSS masks/clip-paths/mask-borders, and feel confident that I could apply them on future client work, so it's not too horrible.
]]>Those who know me probably know that I spend a disproportionate amount of time thinking about the state of the Internet. You don’t have to be chronically online to know that it feels harder than ever to get the information you’re looking for. LLM-generated content is not the sole reason behind this recent phenomenon, but it certainly plays a key role in the sharp downward trend in the quality of information available these days. This article aims to cover the key kinds of information that we as internet users tend to seek out, and how AI is ruining all of it.
I’m definitely the kind of person that needs to thoroughly research any significant potential purchases before I make them. I have a list of sources I feel I can trust for different kinds of purchases, but I also seek out reviews from complete strangers, often from niche online communities. Although I don’t tend to take any single review as the sole reason why I should or shouldn’t buy something, this kind of anecdata often provides a different kind of information that’s not always evident in a product’s claims.
For example, if I’m looking to buy an ergonomic chair and I read a review that says “I’m a 6’3” man and this chair is absolutely perfect for my size,” this anecdote provides helpful information to me in a way that simply reading the measurements does not.
I shouldn’t need to explain why LLM-generated reviews, testimonials, recipes etc. are worse than useless. The main value from this kind of information comes from the fact that another living, breathing human has tried it. You may not necessarily agree with their take, but a made-up review is essentially garbage that litters the landscape of information.
Fake reviews were already a problem when you needed a human to write them, and now that you’ve cut out another step in the process of generating fake reviews, the problem is worse than ever. A significant amount of time will be spent trying to fight and filter out the bad actors who have no problem with polluting the internet for a quick buck.
I’ve been around long enough to know that the line between ‘objective truth’ and ‘subjective’ is a lot blurrier than some might realize, but for the purposes of this section, I’m calling what is generally agreed upon by the vast majority of people to be ‘factual information.’
Questions like “what time is this flight departing” and “are grapes safe for humans to consume” may differ slightly in terms of how many people can come to an agreement, but there is some general consensus that can be reached.
Sometimes, people just want information presented to them as quickly and as accurately as possible. At first glance, it may seem like LLMs are great for this. We’ve all seen cases where asking a LLM a question returns information that is both accurate and detailed. I have no doubts in my mind that LLMs are capable of providing information that is useful to users, some of the time.
What concerns me the most is when things go wrong. People often understand LLMs to be all-knowing, and it doesn’t help that they are often marketed this way. What some people do not know is that content summarized by AI may contain ‘hallucinations’, AKA factual inaccuracies, dressed up in a cutesy term to make their existence less egregious.
The rate of hallucinations appears to be far higher than some might believe. Vectara, an AI company (take this with a grain of salt!), has created a hallucination evaluation model which aims to test the rate of hallucinations across popular models. To summarize their methodology very briefly, they used different LLMs on their most precise setting to summarize a specific set of documents. The hallucination rates ranged from 3% - 16%, which doesn’t sound too horrible in terms of raw numbers, but should not be an acceptable margin of error on factual information.
Josh Collinsworth sums it up really well in his blog post, I worry our Copilot is leaving some passengers behind.
Why do we accept a product that not only misfires regularly, but sometimes catastrophically?
Let’s say you went out and bought the new, revolutionary vacuum of the future. It’s so amazing, it lets you clean your whole house in half the time! (Or so the vacuum’s marketing department claims, at least.)
Let’s say you got this vacuum home, and sure enough: it’s amazing. It cleans with speed and smarts you’ve never seen before.
But then, you start to realize: a lot of the time, the vacuum isn’t actually doing a very good job.
You realize you spend a lot of time following it around and either redoing what it’s done, or sending it back for another pass.
In fact, sometimes, it even does the exact opposite of what it’s supposed to do, and instead of sucking up dirt and debris, it spews them out across the floor.
You would find that entirely unacceptable. You would take that vacuum back to the store.
If I had a watch that told me the incorrect time 3% of the time, that watch would be useless to me. If I used a travel agent and they gave me incorrect information, resulting in me having to pay more for my flight tickets, I would never use them again. The fact that Air Canada’s AI chatbot has made this very error on their own website and they refused to take responsibility for this mistake is ludicrous.
We as consumers should not accept companies rushing to implement AI chatbots in order to save on labor costs, and we especially should not accept those same companies refusing to take responsibility for the incorrect information that chatbot spews.
While there has been progress in cutting down on hallucinations, the remainder of the work is an uphill battle. You see, LLMs have an element of ‘randomness’ built in. To simplify a complex concept that is better explained by others, LLMs are not deterministic. Putting the same input into an LLM will result in slightly different outputs each time. This is part of the appeal, as it allows people to mass-generate content that is ‘unique’ each time, but results in a non-zero percentage chance for error.
I genuinely fear that one day, someone in a moment of crisis is going to rely on LLM-generated content for a life-or-death question such as ‘how should I help my drunk friend who passed out’ or ‘what should I do when someone is having a seizure’ and get inaccurate results. Even people who would normally think critically about a piece of information may be vulnerable to incorrect information in a moment of crisis. Letting something that cannot be fully verified or controlled run loose to spew misinformation seems counter to our jobs as designers, designing for people.
LLMs have a really bad time with information that has inpretive intent behind it. For example, blind people rely on alt text, a written description of an image, so that their screen readers can read it out to them in lieu of being able to see the image. Certainly, some alt text is better than no alt text, but AI-generated alt text typically lacks the context behind why an image is included in a site.
For example, take this image from Shutterstock. (I’m linking it so as to avoid paying to include the image on this site, sorry!) The included description for this image is as follows.
Group of Asian people enjoy and fun outdoor lifestyle hiking and camping together on summer travel vacation. Man and woman friends having breakfast drinking brewed coffee near the tent in the morning.
Certainly, this is a valid description for this image. I have no idea if this was human-written or AI-generated, but I would say it is perfectly serviceable for the context of a stock photo website. However, this description does not necessarily represent the reasons why people might include this image in their own content.
There are as many ways to describe this image as there are ways to use it in context. Even if AI-generated alt text is accurate, it lacks context. Things like memes and jokes are especially ‘hard’ for LLMs to understand (written with irony here, given that they don’t actually have a consciousness!). To illustrate this point, I asked Copilot to explain the first joke I could find on the /r/Jokes subreddit.
Me: Explain this joke: My teeth were stained, so the dentist asked me, “Do you smoke or drink coffee?” I told him I drink it.
Copilot:> Okay, I can try to explain the joke to you. The joke is based on the ambiguity of the word “it” in the sentence “I drink it”. The dentist is asking the person if they smoke or drink coffee, because both of these habits can stain the teeth. The person replies that they drink it, which could mean either coffee or smoke. The joke is that the person is implying that they drink smoke, which is absurd and nonsensical. The joke is meant to be funny because of the unexpected and ridiculous answer. I hope this helps you understand the joke better.
Copilot’s explanation is partially correct. It correctly states that the joke is based on the dentist’s wording being ambiguously interpreted as either smoking or drinking coffee. However, it misses the mark in saying the person is implying they drink smoke.
I hope the joke example clarifies why interpretation is so important when it comes to understanding information. Sadly, I know there are entire masses of people out there who are excited to give even less of a shit about disabled people than they already do. They are excited to automate this alt text problem away so they don’t have to spend a single second thinking about what they as a sighted user do not benefit from. They don’t realize that crafting alt text is an art, I’m not ready to hand over art to the robots just yet.
There’s a type of content that I’m having a hard time describing in a short phrase. Essentially, by ‘educational content’, I’m referring to content that does have factual elements, but the primary value of the content is that it’s presented in a way that really enhances a reader’s understanding of the material. It’s not necessarily content that comes from educational instutions. The author may have put their own spin or understanding on it in a way that reading a summary of the information misses out on a lot of the value it brings.
I consider Ahmad Shadeed’s piece on Designing better target sizes to be an absolute masterpiece in this genre. The article is chocked full of interactive examples, techniques and caveats that is helpful to a wide audience, regardless of background. The language is simple enough for a reader with no prior knowledge to understand, but the information is detailed enough that experienced readers can pick up a new insight or two. It’s what I aspire to with my technical writing.
As someone who’s dabbled in writing educational content, it’s not easy to create content like this, with good articles taking days, weeks or even months to finish. The thing is, it’s already really difficult to find people willing to pay for this content. Inside the tech bubble I work in, with the exclusion of paid software, there’s often not enough money going around to pay technical writers. Although technical writers must be both technical enough to understand the subject matter on a deep level, and good enough of a writer to be able to communicate that knoweldge, it’s simply really hard to get the same rates you would for writing about code as you would for writing actual code.
From what I’ve seen, LLMs aren’t good enough to create something as helpful as a good piece of human-authored technical writing, period. Try as I might, I could not get Copilot to output something about target sizes that even came anywhere close to the article written by Ahmad. The problem is that people believe that it can, thus shrinking the already tiny pool of jobs for good technical writers. Even MDN, a resource that I’d wager every single web developer has looked at at one point, felt that AI was worth implementing on their docs, though it was met with much public outcry. In an age where documentation is being replaced with discord servers, it feels like the trajectory of software development is moving towards reviewing the code output of AI, and when the aforementioned code doesn’t run, ask the AI chatbot for help. Pretty bleak.
Unfortunately, the cat (or the Bullshit Generator) is already out of the bag on this one. We can’t really take it back at this point, and the message from the technocrats is clear, adopt or fall behind. The personal impact of failing to adapt can be devastating when your entire livelihood depends on meeting a certain level of producitvity. My only bit of advice is to push back in your own way. Whether it be fighting unethical use of AI at your workplace, occasionally writing a blog post, curating a list of human-authored content, or logging off, my hope is that we all do what we can to stem the rising tide.
But in the end, it always came down to us, the project team, to prevent the implementation of deceptive patterns that would erode users' trust. It was small things, like offering a free service in exchange for email addresses that clients would blast newsletters to without explicitly collecting consent, or a client who works with marginalized people wanting to place a behavior tracker that logs personal user information.
Many of these things are commonplace, even the norm on other websites. A good number of users are ok with these patterns (or at least, an amount of users large enough to make a difference on the bottom line). When discussing these issues with clients, I could sense confusion as to why I was fighting for the hypothetical users who would care about these things, when most users either don't know or don't care. For a web practitioner who cares about the craft, using a website these days can feel like a death by a thousand cuts. The most horrifying of all to me, is that the average user, even those with a strong sense of ethics and justice, doesn't even know what a good, ethical website is anymore. Being coerced into handing over your money/data and having your personal information tracked and sold is now par for the course.
So how did we end up here? To a casual observer, it may seem that somewhere along the way on the grand journey of building the web, the knowledge of how to build good, ethical things has been lost, buried with our Myspace and Geocities sites.
But is that really the case? Every day, I get to work with people who do good work, who care about things like privacy, ethics and accessibility. We exist. We just are drowning, drowning in algorithmically-guarded walled gardens that are nothing more than quagmires of enshittification, of AI-generated content, of snake-oil solutions. It's like a walking into a jet spray of bullshit, so much so that even those with good intentions get easily overwhelmed.
Though I try, my efforts rarely bear fruit, even with the most well-meaning of clients. And look, I get it, no on wants to be the tall poppy. It's hard enough to squeeze money from the internet-stone these days. Why take a stance on a tiny issue when your users don't even care? Your competitors certainly don't. I usually end up quietly acquiescing to whatever demands are made, praying no future discerning user will notice and think badly of me.
It's pretty clear to me that we can't rely on individual people to make a difference here. Unfortunately, large-scale, societal measures don't seem to work either. Remember GDPR? It was supposed to save (some of) us from invasive tracking by giving users the power to choose how their data was collected. Now, everyone just hates the clutter of cookie popups and there is an entire industry built around helping companies to comply with GDPR in a way that makes it as hard as possible for users to express their newfound rights. When big corporations like Apple spend a huge amount of time and resources actively fighting the idea of a better Internet, there doesn't feel like a lot a single Person-Who-Gives-a-Shit can do.
I don't really know what the answer to any of this is. I just know that I (belatedly) enter 2024 with a whole lot of burnout and an increased resolve to be more precious with the limited time and resources I have. If you need someone to build you a website, or gripe about the state of the web, I'll be over here in my corner.
This was a short piece on a huge topic. Here are some recent posts I enjoyed that touched on similar issues.
Thank you to my friend Becky for her thoughts on an initial draft of this piece.
Readers Of The Blog™ may know that I have been experimenting with the View Transitions API as a way to provide animations in my new portfolio site. Once I understood the basics of how View Transitions work (in Dave's words, think Animorphs), I found it relatively simple to get up and running with the default morph animation. In my case, I'm trying to replicate how an operating system will shrink and fadeout a window when you minimize it, within the same page. However, shortly afterwards, I noticed that there was a small issue, the animation was actually playing on top of the footer. Not ideal!
I know this can be hard to see at full speed, so I've quickly thrown together a little interface below so you can adjust the playback speed in browsers/feed readers that don't show a playback speed toggle by default. It's probably a little janky. My apologies.
Unsure of whether or not this was just my implementation, I tried to track down other examples in which this issue occurs. I was able to reproduce this in Astro's demo for View Transitions as well. Given that Astro got a nice little shoutout on the Chrome Developer blog, I presume it meant someone from Chrome did take a look at the implementation at one point, so seemed to point something to do with the browser rather than developer error.
If you couldn't spot the problem, here's a static screenshot of the demo at the exact point of failure as well:
I sent out a few feelers in the ever-helpful Shoptalk Show Discord and tried various things with no success, such as adjusting the z-index, transform and will-change properties on the animating elements, but without any success whatsoever. I ended up filing a bug report for Chromium, where the I was directed to this Github issue with a detailed explanation from Khushal Sagar, a Chromium engineer, which is worth checking out.
Basically, here's my understanding of the issue, from a non-browser developer perspective. View transitions aren't actually moving your elements around in the DOM, the browser is doing secret work to take a snapshot of what the elements would look like as a flat raster image, and doing Animorphs Magic (Thanks Dave!) to morph between these images.
Paint order, the order in which your elements are painted, cannot be easily calculated without applying a complex algorithm, especially since many uses of View Transitions involve transitioning between pages. There isn't really a way to figure out the shared stacking context between two pages, because if we had to consider the z-index of every page simultaneously to account for every possible transition, our heads/computers might explode. The compromise that exists is that the browser tries to paint the elements in the following order (paraphrased from Khushal):
You will note here that elements without a view-transition-name are not included in this paint order. I didn't have a transition name on my footer, as I had only worked on a single page, and footer had no animation at all. So, in order for my footer to be included in this paint order within this animation, I just had to stick a view-transition-name
property onto the footer, even though it's not actually being transitioned.
If your use-case is slightly different and you are transitioning between elements that don't exist, whether it be due to an element being hidden with display: none
or transitioning between pages with unique elements such as in the Astro demo, you will want to make sure that:
view-transition-name
property on it.This results in code that looks like this:
.myFixedFooter {
position: fixed;
view-transition-name: footer;
}
::view-transition-group(footer) {
z-index: 100;
}
Initially, I was confused as to why things weren't working in the way that I expected. It felt not intuitive to apply a transition name to something I wasn't transitioning, and I wasn't sure why my z-index outside of the transition had no effect on the transition!
However, after reading through the explanation and finding the workaround, I'm personally okay with this limitation for my use case. Z-index + transitions has always been slightly iffy, and in this case, I'm just really glad there's a workaround.
]]>Feeling ambitious, I write a Svelte store that allows me to manage the minimized/maximized state of all the windows. Unfortunately, I notice that the order in which the window ‘registration’ occurs is random, and I’d like it to reflect the order on the page, so I spend some time trying to coax it into order while cursing my existence. I end up with a workable solution that doesn’t take too long, as I’m personally ok if the window list at the bottom of the page comes in after JavaScript renders as it’s mainly for ‘flavor’. If it was a part of the main content, I probably wouldn’t accept this tradeoff.
Deciding that I’m done with JavaScript for the day, I move on to some of the design aspects. I first establish the kind of button style I want for the UI buttons, which requires attempting to rewrite the pixel-borders mixin. I successfully refactor the mixin to use relative units, so that it scales nicely with the rest of my UI, but I cannot get it to take my CSS variables as a color input. This is because the mixin uses SVGs within a CSS url attribute, and these are treated as as external documents. I experiment with using mask borders instead of border images, but the browser support is pretty bad. Defeated, I accept that my design system will no longer be ‘perfect’, and accept that I have to pass in a hex code for my borders.
I throw together a list of client logos and hope no one gets too mad at me for ruining their brand. I’m going for the crunchy 88*31 blinkie look, though I decide to increase the size for legibility. Think geocities, but purple.
I hastily scribble together a close icon (a little ‘x’) to see what it looks like in context. I fall down a bit of a rabbit hole, making many different versions of the icons across different sizes and resolutions. My normal SVG workflow is to run them through SVGOMG, but this has a disastrous effect on the pixel art style. The default export from the spriting program I’m using draws individual rect elements for every pixel. As you can imagine, this doesn’t generate the smallest file size, but preserves the pixel art as one would expect. SVGOMG converts the rects within the svgs into a path, which smooths the whole thing out. While this behavior is normally desirable, this is exactly what I don’t want for this project.
Through my experimentation, I discover the image-rendering: pixelated
property does exactly what I want it to as well! I now have a suitable method for rendering both SVG icons and raster images! I can pick and choose based on the file size, since the SVG exports won’t be as tiny as they normally are. Chuffed with my progress, I leave it here for today.
Day 3 and day 4 happen with a huge gap between them. This is because I feel the need to flesh out the illustration work a lot more before moving forward. I’ve been working on a client project where illustrations were involved, and it was difficult to progress with the final design work until the illustration style was fully locked in.
In my case, this meant working on at least one of the ‘desktop icons’ as well as the background. This may lead you to think I spent a whole bunch of time painstakingly crafting the illustrations, but actually I just procrastinated until I felt too much guilt and finally opened up Aseprite, the new pixel art editor I bought just to make these illustrations. Sometimes, I find myself needing to invest in something that will help me get stuff done as a motivational tool.
I’m not entirely happy with either the background illustration or the icon, but now that I have a sense of direction, it helps me out quite a bit. I adjust some colors to make sure they meet contrast requirements, something that was hard to do without having a background in place. Right now, I’m dumping all my images in the repository instead of Cloudinary, because I can’t make any decisions. Much to my chagrin and embarrassment, I’m up to 8 versions of the close button alone, and 5 versions of the background. I hate to say it, but I'm up there with the most indecisive of clients.
The particularly observant of you may note that although I have 8 versions of the close button, but don't have any close buttons at all in the screenshot above. Fear not, they will return again, on another page! I think it doesn't make sense to have the windows be closeable on the homepage, so yeah.
I did a lot of work developing the window component and how to make it usable for various different contexts, like what happens if there’s just one content window vs. many, how they stack on top of each other and etc. This didn’t result in a lot of ‘visible’ changes, but it helped me feel more confident in the overall direction of the project. If I know the fundamentals work, I hopefully won’t reach a scenario where the entire UI idea falls apart. Hopefully.
I spent a long time playing around with the new View Transition API to animate how the windows would maximize and minimize. For the most part, it looks really slick when it comes to moving things around, even with the defaults. The one animation I don’t like is the maximizing window option. I was expecting the text to reflow like how the browser does it, but since learning that the API basically transitions static images, it meant the text scaled up statically, then janked back into place, and there would be no way around this.. I opted for the default cross-fade animation for maximizing instead. I know there are ways to make this work with animation libraries, but I didn't want that kind of overhead.
Writing that last paragraph sent into a small existential crisis as I contemplated if Svelte was the right technology to use. I like Svelte and enjoy building with it, but I wonder if I would have been better served by making the Window svelte component a web component. Putting your money where your mouth is, as one might say. The premise seems simple, but I don't feel too optimistic, as there is quite a bit of state that needs to be managed externally (ie. the footer windows), and I do need to reach out of the component to calculate some sizing and positioning attributes. I tell myself it was the right decision to build it as a Svelte component, and cry myself to sleep.
I know I wanted to do something with a lot more fun and flair, while still remaining accessible. I know a lot of designers feel ‘constrained’ by thinking accessibility, so I wanted to build something that showed that you can still build something fun and beautiful while thinking about accessibility. I have a deep love for pixel art and retro games, and felt with the View Transitions API coming out, I could experiment with building some kind of operating system like interface for my new site.
Folks have occasionally asked me to share my work process, but I typically work on sensitive projects that I can’t share too many details on. Writing about this process as I go about it provides a rare opportunity to share a little about what I’m doing for once, so I hope you enjoy! Feel free to send me a toot on Mastodon or an email if you enjoy this series so I know whether or not I should keep writing these.
I am not a designer, and I have limited ability to use any design software to produce designs. When I am in a position where I do have to design, I tend to come up with stuff more iteratively. I typically tend to only nail down the broader stroke stuff first like colors and type, then I refine as I go on. I looked at pixel style video games, ‘vaporwave’ UI designs and the good ol’ Windows 95 UI to in order to get a sense of overall design language.
Because I liked my current site colors, I grabbed all my primary brand colors and shoved them into this color scale generator. I tinkered with it to make 2 lighter and darker tints/shades for each primary color I had, then I plugged everything into my all-time favorite, Are My Colors Accessible to make this monster color chart that I could reference to find accessible color combinations.
I did a few font tryouts but settled on W95FA as my main font. I liked how legible it is compared to other pixel fonts, but I also knew that I probably needed to make a toggle to allow for a high-res font in case anyone struggles with reading it. After perusing Modern Font Stacks, I settled on the 'Humanist' font stack as a nice fallback font stack.
I consulted the hivemind over in the Array Discord on sites with great about/services page, since that was an area of my site I felt was particularly lacking. Special shoutout to Steven Woodson who gave me a list of references he consulted when he went through the process of rebranding recently. Having some sense of what the copy on each page looked like made it much easier to wireframe, I'm very much a fan of designing the site to fit the content you have and not the other way around. I ended up scribbling some very loose wireframes with good ol’ pen and paper.
Finally, onto the actual development!
@mixin dark-colors {
// all my variables here
}
@media (prefers-color-scheme: dark) {
:root:not([data-user-color-scheme]) {
@include dark-colors;
}
}
[data-user-color-scheme="dark"] {
@include dark-colors;
}
max-height: 100dvh
on my grid container, the main content grid item decides to bust out of it. I spend an hour or two pouring through stack overflow until I found this answer, which informed me that grid items have an implicit min-height: auto
on it. I set min-height: 0
on the main window pane area and the layout snapped into place as it should. Who knew!Hi, everyone, my name is Nic Chan and today I'll be speaking to you on "Developing an Accessible 'Add to Cart' Flow." If you have any questions or comments, you can find me on my website at www.nicchan.me. That's spelt N-I-C, C-H-A-N, or on my Twitter handle, @NicMakesStuff. I'm a Hong Kong woman with wavy black hair, and I'm wearing a purple an Axe-con shirt much like Liz. To give you a brief introduction, I'm a freelance front-end developer. And the insights from this talk are sort of gleaned from my years of experience working on Shopify stores. But hopefully the insights can also be applied to other e-commerce platforms. And fun fact, I'm definitely a cat person.
To give you a sense of how this talk is gonna go, today, I'll be covering four different patterns, first being buttons, then inline notifications, then toasts, and then dialogs. And for each pattern, I'm gonna walk you through some different iterations, so some different variations of what these patterns might look like in the wild. And then pros and cons for each pattern, as well as tips and tricks for implementing them in a more accessible way.
So to make sure we're all on the same page, here's a typical Product page. I'm sure many of you have seen some variation of this in the wild before. On top, we have our breadcrumbs, which is our links that point to different parts of the website. Underneath that we have our slide images, and some thumbnail images. On the right, we have a product title and a product price, we have some variants for selecting a different color. And below that we have our star of today's show, it's the Add to Cart button. Underneath that we have a product description. There's lots of things to comment on this with regards to accessibility but for now, we're just gonna be focusing on what happens after that Add to Cart button.
So the first thing we wanna do is make sure that we're always using a form element. The reason why we would wanna do this is for two main reasons: progressive enhancement and semantics. There's this common misconception that screen reader users can't use JavaScript, but that's just a myth, it's not really relevant. The main reasons are for progressive enhancement and semantics. Progressive enhancement being the idea that we want our site to work for as many users as possible. So if for some reason the JavaScript fails, we'd still want our key actions to be able to be performed by our user and that includes our Add to Cart. In terms of semantics, assistive technologies offer the functionality to jump to things like different elements on the page, including form elements.
So by including a proper form element here, we can provide users that functionality. It's much like a table of contents in a book. So if someone wanted to skip to a particular point, they can just browse that table of contents, and then jump to what they need instead of just having to listen to the entire page before reaching that form. So what happens with this default form example? Well, we can see that the form element has an action attribute on it, which points to a /cart/add route.And when you hit that Submit button, it'll take you to that /add route and it'll add that item to your cart and then presumably, it's gonna redirect you to a cart page. This solves a whole bunch of accessibility issues for us. Because navigating to a new page is a fundamental interaction of the web, almost all assistive technologies support that and your users understand that, okay, like we're on a new page, it's titled your cart, and I can see that the item is now in my cart, and I understand what has just happened here.
So if this approach is so robust, why would we ever wanna use an alternative? Merchants realized that this was kind of like pushing users into a single flow, it's forcing them to Checkout when they might not wanna always go to checkout, they might wanna continue shopping on the site or do some other action first. So the following patterns are kind of developed as a way to both notify users and offer them a choice of whether or not to continue shopping or proceed to checkout. Now that we're all on the same page, let's move into the actual patterns.
The first pattern is a button pattern. And here I have an image. And on top, we have our first button, and it just says Add to Cart. Underneath that we have a second button, and there's a checkmark in it, and it says Added to Cart instead of Add to Cart. When I refer to the button pattern, I'm referring to when the buttons text is changed when it is activated. This is a pattern that can be used in combination with other patterns, and I'd recommend it for the following reasons, and that is super great for users who use magnification, whether it be the default browser zoom, or special zooming software.
So here I have a screenshot of a user who is using ZoomText to browse our page. And on this, they can only see a small region of the page, they can only see that price, and then the color variant, our Add to Cart button, and then one or two lines of description, so they only see a pretty small region. It's really good to have an indication of the state of the page in the place that they're currently looking at. So if we use the button pattern it might look something like this.
And here we can see that very same region of the page and our Add to Cart button now says 'Adding'. So moving on to implementation details. This type of pattern is typically implemented by using JavaScript to change the text.
So here we have, and we have our form element. And then below that we have some JavaScript where we're just adding the item to the cart, and then we're changing the inner text of that button to say 'Adding'. In isolation, this isn't a recommended approach because screen reader users aren't that great at handling text changes consistently. So while some screen readers may support changing the inner text of a button that's focused, a lot of screen readers don't. So we can consider using live regions in order to provide better support for screen reader users. If the button is your only indication of state, you definitely want to be using live regions.
Live regions are a way to announce changes in your site to assistive technology users. You can kind of think of them as a web equivalent of the inflight announcement on a plane, like you're just watching a movie, you're having a good time, and then suddenly, the captain's voice just interrupts and tells you that you're about to land. That's kind of like what a live region is. There are only for text only announcements, you can't really do anything with them. Live regions can be created by adding the aria-live attribute with a value of either polite or assertive. By using polite, you're telling the assistive technology for the current text to be finished before announcing what is in the live region whereas if you use assertive, it'll just interrupt the user, no matter what they're listening to. Live regions work most consistently when they're already present in the DOM. You don't wanna be adding them dynamically using JavaScript, you typically wanna have a live region already on your page, and then use JavaScript to add or remove text to it whenever relevant.
So here we have our code sample. And we have our form element. And below that we already have a live region with the attribute aria-live="polite." And below that we have our JavaScript. When we add the item to the cart, we're grabbing that live region and we're grabbing that button. And we're changing the text of both of these elements to be the same thing. Sometimes developers might wanna disable that Add to Cart button while the action is being assessed in order to prevent double submission. So sometimes users accidentally click it, click the Add to Cart button twice, and then you have two items in your cart instead of one. And developers often use the disabled attribute to kind of prevent this. Using disabled isn't always the best choice because if you're not managing the focus correctly after, you can cause the user to lose focus, and then lose keyboard focus.
So an alternative might be to use the aria-disabled="true" attribute instead, and use some additional code and styling to make sure that the same functionality is provided. But using aria-disabled might really not be any better because what can be announced to screen reader users can sometimes be confusing. For example, a screen reader might say something like Add to Cart unavailable. And that's especially confusing in an e-commerce context because, you know, how will the user know if the button is unavailable, or the product that they're trying to buy is unavailable.
So in this case, you would really wanna consider your user base. If your website is selling to a technical audience, maybe your audience will have a higher level of technical proficiency and they'll understand that this is actually unavailable as being applied to the button and not the product, and it will be okay. But you'd really wanna consider testing with users whether or not the disabled attribute is even needed or if you should just disable the click with JavaScript.
One final consideration for the Add to Cart button is wording. So here we have a diagram of two different possible wording flows. On the left, we have a button that says Add to Cart, and then it says Adding, and then it reverts back to Add to Cart. This can be confusing if it's the only indication of state because it requires users to be very actively paying attention to what might be a small difference. You'd have to imagine your user just staring at that region of the screen. And if your website loads fast as hopefully it should do, it's gonna revert very quickly back to the original Add to Cart state. And if that's your only indication, it's not too clear that anything has happened.
On the right, we have a button that says Add to Cart and then Adding and then the final state is Added to Cart. This poses a different problem of if you want to reset that button to the original state. There tends to be room for confusion about now, it's pretty clear that an Add to Cart button allows you to add something to your cart but what does an Added to Cart button do? Does it take you to Checkout? Does it let you add that item again? It's a mystery. So we might wanna consider using the button pattern in combination with other patterns in order to alleviate some confusion. And this leads us to our next pattern.
What do I mean when I refer to the inline notification pattern? By inline notification, I mean an element that is not positioned sticky, absolute or fixed. A notification is inserted into the reading area and then content is often shifted in order to make space for this notification.
So here we have an image. It's our same Product page as before and this time, underneath the Add to Cart button, we have a notification that lets us know that our product has been added to the cart. So we've already discussed the idea of using live regions as a way of presenting content to assistive technologies, should we be using them here? Live regions can best be thought of as temporary notifications.They're the web equivalent of an announcement blaring at you over the loudspeaker. If you're not in a logical place, like how I've placed it right under the Add to Cart button here, it might not be obvious where in the DOM they are, and a screen reader user has no option to go back and listen to that text again. Furthermore, zoom magnification users who don't use accompanying audio could be left out and they could miss this notification entirely.
A better approach would be to programmatically move the focus to the notification once it enters the DOM. And then this will be announced to screen reader users and other groups. So here we have our code samples. And we have a notification that has a tabindex of negative one,which lets the browser know that this element will be programmatically focused, but not included in the default focus order. And below that, we have our JavaScript and we're once again we're just adding that item to the cart and then we are grabbing our notification, the text of that notification to what we want it to be and then we're moving focus.
Sometimes our notification isn't just text, but it also includes interactive elements. So here we have the same image as before with our Add to Cart button and then below that is our notification. This time, I've added two links to this notification. So underneath that notification text, there's a link that says Continue Shopping. In these situations, we have to move focus, because our aria-live regions strip semantics of child elements. So a screen reader user might be able to tell that these are actions from the words, but they're gonna hear the words Continue Shopping or View Cart that has interacting with them.
Some implementations will have this notification disappear after a short amount of time. While this is not advisable due to being easily missable, it's also a WCAG failure of the 2.2.1 Timing Adjustable Criteria. Essentially, this criterion exists to make sure that we're allowing people to have adequate time to complete interactions. Because we're providing different interactions within the notification, hiding the notification automatically can prevent users from completing them. So a better way around this would be to add a button to dismiss the notification manually instead.
In the wild, I often see different placement options for this pattern. So here we have two examples of where we might put our notifications. And the first one is directly below our breadcrumbs, but above all of our product stuff. And the second one is in a similar location to the original notification, but it's directly above the Add to Cart button. I'd argue that the best place for the notification is after the Add to Cart button. Well, if we're moving focus, technically, we meet the requirements either way, but we can improve usability by not taking users too far out of the context they were originally in. Placing it near the button helps minimize tab stops for keyboard users and it will disorient users with dramatic content shifts if you're kind of like shifting things around the page. If we put it under the button that they're probably looking at, we have less room for disorientation.
The next two patterns I will be discussing are toasts and Dialogs. Before I get into the details, I'd like to emphasize that these patterns often lack distinctions in the wild. I'll be describing the behaviors of both toasts and Dialogs in terms of how they should be behaved, but the lines are really blurred. Because they pop out on the screen and overlay content, people kind of just tend to see a toast as a little dialog, and they code them in a really similar way. This ignores distinctions and semantics and the requirements for each of these elements to be accessible. A lot of accessibility errors come about from developers treating these two components as interchangeable when they're actually quite discreet.
So what is a toast? A toast is named after the behavior of a toaster.It's an element that kind of pops up in a fixed position on the screen, typically in the top right, bottom right,underneath the nav bar, and other places like that. And they should have a role="status" or role="alert" attribute on them, or an aria-live attribute so that they're announced to assistive technology users. Focus typically isn't moved to a toast. Contrary to how toasters work in real life, toasts on the web often disappear after a few seconds.
Should we be using toasts in an e-commerce context? A lot of the problems with toast stem from the fact that by definition, focus, isn't moved to it. Furthermore, toast inherently have a problem shared with a lot of fixed position elements, they're really easily missable by zoom users. You can imagine that if we're zoomed into that Add to Cart button and something pops up in the top right of the page, it's not that useful for a user who's just looking at that Add to Cart button region. Here we have a visual and we have a example of a toast.
Our toast looks really similar to the inline notification that we have previously but it's overlaying our content and it lives on the top right of our page, and it disappears after a few seconds. Early on, we discussed the 2.2.1 Timing Adjustable Criteria and the problem with child elements and this applies very much to this example right here. Trying to interact with a notification that disappears is about as frustrating as your toast vanishing off your plate while you try to eat it. Not recommended.
So how do we make sure that we don't fail the 2.2.1 Timing Adjustable Criterion while keeping tools behavior? We have some options. The first is that we could have a setting that lets you set that notification display time. And here we have an example of a settings page where we just allow the user to set the notification to five seconds. On the right, we have an example of a notification drawer and it looks a lot like a notification jar on your phone. Basically, it shows you all the toasts that have popped up previously with a timestamp of how recently they popped up. Sure, we can use both of these options to kind of remediate the issues with toasts, but it sort of defeats the purpose of toasts as a quick and easy way of displaying things. Plus no one really wants a drawer full of stale toasts. Unfortunately, I don't really think that toasts live up to their real life namesake. They're neither as useful nor as delicious as their real world counterparts, especially in e-commerce contexts where the Add to Cart is the primary action. What developers often try to implement as toasts should really be dialogs instead. And this leads us to our final section.
Dialogs. Dialogs are often a misunderstood component, there is a misconception that all dialogs are basically what we understand to be an alert. Here on the screen, I have an example of a Windows 98 Style dialog. And it basically says "The system registry has been backed up already today. Would you like to back it up again?" And it provides option of Yes or No. All of the following patterns are examples of things I regularly see on online stores. While visually they look really different, they can all be considered dialogs. And so they can use similar semantics and functionality underneath the visual styling.
This is a dialog. So what we have here is a Product page that we had before and we have something that looks really similar to our toast example. It has "Your product has been Added to Cart" text, and it has our to Continue Shopping or View Cart links. And the only difference is that we've now added a little Close button in the top right so a user can dismiss it.
This is also a dialog. So here we have a Products page and there's a white semi-transparent overlay on top of that page. And on top of that, we have a kind of more expanded dialog. And we have our "Product has been Added to Cart" text, we have an image of product and then below that, we have our Continue Shopping and View Cart links styled more prominently. And there's also a Close button here.
This is also a dialog. So here we have a Product page and the difference is now we have a navigation bar and in the top right we have a button that will trigger the cart, and below that, we have our dialog and it has a title of your cart and within it, it has all the products that are in our cart, the total price, and then the option to Checkout.
This is also a dialog. So here we have our Product page, we have our semi-transparent overlay overlaying the content. And on the right hand side of the page, we kind of have like a cart and floor type situation that slides out from the right. And we have a title of your cart, all our products, the price and the Checkout button. The primary difference between these dialogs is whether or not they're modal or non-modal.
Non-modal dialogs don't prevent interactions between the rest of the webpage when they're open, but modal dialogs do. So if you have a backdrop behind your dialog, that probably would suggest that it is modal and your dialog should be coded in a modal way.
Now that we know that all these seemingly different patterns are actually just dialogs under the hood, I'm gonna give you some advice on implementation details.
It's my opinion that if web development were a video game, hand rolling your own dialog would be the final boss. I'm not gonna go into all the intricacies of building an accessible dialog because it is super complex, but here are a few tricks that can help you when evaluating dialogs on your site and perhaps letting you know if your current solution is okay or if you need to find something else.
Focus management. So regardless of whether or not your dialog is modal or non-modal, we wanna be moving focus to the dialog. If your dialog is modal, you also want to trap focus, which means that you shouldn't be able to move focus to the document that's in the background. When the dialog is open, hitting Tab a whole bunch of times suggests cycle between the elements within the modal and then shift to the browser UI. This browser UI is often forgotten part of the focus trapping.
When we close the dialog, we should return the focus to the element that opened the dialog. In this case, it's our friend the Add to Cart button. But if you've opted for a mini cart, or some other kind of cart thing that can be opened through another part of the page, you'll probably wanna account for handling different mobile triggers and moving focus to the correct element that opened the modal in that scenario.
Keyboard. We don't wanna forget our keyboard shortcuts. When you hit the Escape key, the dialog should close. If the dialog is modal, we wanna make sure that assistive technology users can't escape from the modal via other means. So oftentimes, I find developers will correctly implement focus trapping, which traps focus within the dialog but you also need to make sure that the document behind the modal is hidden from users when the modal is open. You can consider using the aria-modal="true" attribute in the future but the browser support is not that great right now. So in the meantime, you can probably consider using the inert attribute with a polyfill in order to make sure that the rest of the document is correctly hidden from assistive technology users.
And finally, we don't wanna dismiss the dialog automatically.Well, the dialog element is just about here. I have to give a shout out to Safari,which just dropped an update, 15.4, like two days ago, which caused me to frantically update these slides. The dialog element is so close, it's a great time to begin experimenting with deploying it in the future, but if you're a developer who needs to support older browsers, you might still wanna wait and you can continue using a with a role="dialog" until the browser support is just a little bit better.
Finally we wanna make sure that we're labeling all the things. We wanna make sure we're labeling the dialog itself. Consider using a aria-labelledby attribute, which will point to the ID of the element that labels the dialog. So it might be the text of the dialog or it's a heading like your shopping cart, we just wanna make sure that our dialog is labeled.
It's a really common issue that's found in modal carts and things like that so if your dialog has repetitive interactive elements, so like multiple Increase Quantity buttons, or multiple Remove Product buttons, one for each of your products, make sure that they have unique names. It can be annoying for a screen reader user to just list all the buttons, and hear five different buttons that say Remove with no indication of what product they're actually removing. So we wanna make sure that each of these buttons are labeled with a unique name.
Some final takeaways. I don't think there's a clear winner amongst the pattern. How you might use them really depends on the context of your site. Things like how many products you have, or what you want a user to do after adding an item to the cart, it sort of really drives which one of these patterns you'll wanna use.
And while testing individual components is great, we really wanna test the whole flow. So you can see that when we combine the patterns in different ways, different considerations pop up. So it's really necessary to not just individual components in our design system, but to test the entire flow to make sure that once you put it all together, it works in an accessible way. Finally, toasts and dialogs are not the same.
A whole bunch of accessibility errors come about from treating different components interchangeably. If you only learn one thing from this talk, let it be that toasts and dialogs are not the same. And with that, I'm pretty much done. So I guess I'll hand it back to you Liz for questions.
Liz: Awesome. Thank you so much Nic for a great presentation here. And thank you for adding questions for all of our attendees. Please still feel free to add any questions that you have for Nic in the Q&A portion. And definitely Feel free to upload them and I'll make sure to go with the ones that have the most votes. So let's dive into the first question. This one's on toasts. Should toasts be used for simple success confirmation messages that would have no interactions with it, such as Saved Successfully, Copy to Clipboard, et cetera?
Nic: Yeah, I think if there's any place for toasts, it would be on those simple confirmation messages, as you've just said, especially if a user is gonna be triggering that action several times in a session. Like, if you are, you know, working in Google Docs or working in an art program, you wanna make sure your stuff is saved, and you probably save like 500 times an hour like I do, so in that case, so toast is probably appropriate.
Liz: Definitely. Okay, awesome. Next question here. Okay, this is on Add to Cart button. So could we use the Add to Cart button as a live region, instead of creating a live region and a separate div element?
Nic: I think that would conflict. I think it would cause a conflict because live regions, they will strip semantics from things. So because the button is a semantic element, we probably shouldn't use a live region on it. I'm sure reading the spec of what it's appropriate to add aria-live to will reveal whether or not it's an actual spec incompatibility, or if it's just something that we probably shouldn't do, but I probably wouldn't do it.
Liz: Okay. Great. Thank you, Nic. All right, got some more questions coming on here. Okay, what tools would you recommend someone use who is new to accessibility?
Nic: There are so many different tools for different things, I can't really think of any e-commerce ones off the top of my head. I have written an article for Smashing Magazine that is just entirely a ginormous list of different accessibility tools, and they're categorized by different uses. So for example, there's like tools for reading level, which can help with cognitive accessibility, or tools for color so you might wanna check that out.
Liz: Awesome. Thank you. Okay, another one on toast messages. Should toast messages be removed automatically after a set time, or both hide and remove it?
Nic: So if you add a button to a toast, it kind of becomes a dialog. And that's part of where the issue is. Toast by definition should be removed after a set period of time, which makes them not great for a lot of things, but a true toast would just be removed after a short period of time.
Liz: Got it. Thank you. Okay. Somebody says, thank you for calling out including the browser UI in the focus trap. I hadn't considered that. Do you know of any good resources for how to do that?
Nic: I'm not sure actually. I think some libraries will handle this and some won't. But it would definitely be something that I would test for when looking at it. I can't name any off the top of my head, I would wanna bet that Kitty Giraudel's a11y-dialog Python dialog library should do this, but I haven't looked at it in a while.
Liz: Awesome, thank you. Okay. There's another one. You suggested moving focus to an announcement text. How does that work with the rule to not move focus around in response to user interaction?
Nic: I think because there are certain components that just sort of require you to move focus around, like there isn't really an option to have a dialog and not move focus to it because then the user would basically just completely miss that the dialog is there. I do know which rule you're talking about, I just I'm not entirely sure that applies in this particular context. I think you probably don't wanna be moving it around willy nilly but because this is sort of like on other site, it's not really acceptable that the user misses it. So we wanna make sure that when we do move it in a way that's like respectful and like not intrusive, but I'm pretty sure that we should move focus. - Thank you.
Liz: All right and thank you everyone for these great questions. Let's see here. Should we implement the Escape button to close toasts and notifications or only for modal dialogs?
Nic: That's a good question. I think it could be a worthwhile enhancement but obviously, there could be a complicating factors within your design, that would make it not ideal, for example, we've talked just like a Product page. But for example, I've seen, like, quick shop modals where the Product page is in a modal. So if you have a modal open, you'd wanna like consider, what kind of our modals are we gonna close with Escape, and things like that. So like I hate to give you, it depends answer, but I think in this case, it does.
Liz: Makes sense. Okay, could you follow up with a little more about how aria-live strips semantics from elements?
Nic: Yeah. For example, if we're looking at our notification, and if a screen reader user is just listening to it on the page, they'll hear product is an Added to Cart link, Continue Shopping or link View Cart. So they'll hear that they're interactable elements within it, and they can choose to interact with them, but when you announce it, a live region will basically just ignore all the stuff that you have in there. So if you have a heading in there, if you have links or buttons in there, it'll just read the inner text. So it'll say product has been Added to Cart, View Cart or Continue Shopping, but then the user can't really tell it's a link, except for hearing the words that are kind of like actiony.
Liz: Right. Thank you. All right, this one is on modals. Do you ever use modals for checkout, such as pop up modal for login, et cetera, or is that something that you would just discourage?
Nic: So I've largely worked with the Shopify platform, and they have, I think, just locked all of the checkout to their platform. So there's basically no customization there. I do know that, like, one thing I really respect about the Shopify team is that they regularly have disabled users go and test all their flows. And they don't include modals in their checkout flow as far as I know, but that doesn't mean that you should never use it. I think, in their login situation, they will show a form in place, but they won't show it in a modal.
Liz: Right. Thank you. Okay, what is one piece of practical advice you would give to someone starting out? I think starting out in accessibility.
Nic: Yeah, I think accessibility is really hard. I think that we kind of tell people it's easy so that they won't be afraid of it and then you start to learn a bit about it and it's oh, my gosh, there is so many considerations. And not just that, it's like sometimes people's access needs will actually conflict with each other. So what will be good is actually good for other users. And I think the most important thing is to not be too dogmatic and just listen to feedback and accept that. It's like, Sisyphus's boulder like it's a never ending task and just simply it's part of the process.
Liz: Definitely. All right. What is the best e-commerce Add to Cart flow you have ever come across?
Nic: That's hard to say. I think honestly, I get so surprised when anything even works.Like, it's really sad. But when I try and test these things out,even if it works for just a single select flow, I would be so impressed. Like if there's an Add to Cart process that kind of works just for keyboard users, even if it didn't work for screen reader users, I'd be like, wow, they like made an attempt,because so often, it's just like not accessible at all for any users. When it works, even in part, I get impressed. But I know that's like a terrible bar and I shouldn't be impressed.
Liz: All right, next one, what do you think about using an animation that moves a number up from your button to where your shopping cart total is indicated on your page so it's like counting up?
Nic: I think I've seen an example of what you're talking about. I would say, you know, you can try it out, I would definitely make sure to respect the prefers-reduced-motion query, it's a query that lets you know if your user has turned off animations on their operating system or their browser, and by using that media query, you can make sure that you're not showing the animation to users who definitely don't wanna see that animation. So try it out, remember to reset filters, reduce motion, and then sort of test with disabled users if you can.
Liz: A couple more questions here. Okay, for a modal dialog, would you dismiss the dialog when the users focus exits?
Nic: So for a modal dialog, the focus should be trapped within the dialog. So basically, if you're hitting Tab, you should kind of like cycle through all the tab-able elements in it, and then the browser UI, and then when you're tabbing back into the page, it should just reset on the first element in that modal. For a non-modal dialog, it definitely depends on sort of the presentation of it, but it's pretty standard that tabbing out of the non-modal dialog, you might close that modal automatically. So you can consider doing that for non-modal dialogs.
Liz: Great. Thank you. I think this might be the last question here. Should animated scrolls between two points on a page be avoided? It provides continuity between points, but could also be overwhelming, especially for zoomed users.
Nic: Yeah, I think browsers have recently given us a bunch of tools to kind of smoothly scroll between things and I think as we see that more, we do hear people with vertigo and motion sensitivity, saying that it's not a great pattern for them. So this is definitely a situation where I would make sure to use the prefers-reduced-motion query to make sure that we're respecting that preference, at least, and sort of consider how large that shift is gonna be. If your page is like an infinite scroll, scrolling all the products, I would probably not use that effect for anyone, but if it's maybe like a shorter distance of the page, you might wanna use itwith a prefers-reduced-motion query on it.
Liz: Great. Looks like we have just one more question here. Is it good practice to focus a dialogs Close button first when it opens?
Nic: Yeah, so I think this can really be a point of concern for a lot of people. I think different people will say different things. Some users think it's best to focus the entire modal and if you have a aria-labelledby on your modal, it'll kind of like read the heading first, or what it's labeled by first and then we'll kind of just read all the content in the modal and the user can choose to interact with it however, whenever they want to. Some people might focus directly on that heading elementand some people might just move focus to the first focusable element. I think it could be that you're focusing directly on that Close button instead of the modal container just 'cause you're adding items to your cart, you hit Add to Cart and then the next thing you hear is just like, close modal. It's not entirely clear what has just happened. But this is definitely something that are testing on your site and whether or not your modal, like your modal design you can change what that first focusable element is.
Liz: Got it. Great. Okay, we're gonna squeeze in one more question here. With such a varied range of disabilities, does it make sense to make flows for each and have different views and features for each?
Nic: I think that kind of like gets us into the territory of having like a different site for disabled people. And that can be a really othering experience, You can consider providing different ways of checkout, like, for example, some e-commerce sites you have individual Product pages, but they also have like, a collections list where you canadd items to cart from there or like different ways of adding to cart and perhaps in a way, you can provide different methods for the same outcome and not in a like this is for ZoomText users kind of way but if you have those options, it will. If there's a blocking aspect on one pattern, maybe it won't be as terrible on another pattern.
Liz: Great. Thank you so much, Nic. And with that I think we are about at time, I do wanna thank Nic for a great session. We really appreciate it. And thank you all for attending this session and for joining us at axe-con. We have quite a few more sessions the remainder of the day,so we hope y'all have a great time. Thank you.
Nic: Thanks Liz.
Liz: Bye.
]]>Lately, I've come to realize that I need to do better when estimating how long this kind of work will take. Across all my clients, I’ve noticed varying levels of how much post-launch CMS support a client needs. When I was trying to think of explanations for why some handoffs are smoother than others, I noticed certain commonalities; I realized that it all came down to the fundamental question of who is going to be editing the content in the future.
The smoothest hand-offs are always to those clients who will either have technical folks managing content in the future, or a designated permanent content person/team with previous content management experience. Technical teams tend to be aware of inherent limitations with content management, and in the event that something does go wrong, they may be able to fix it unaided. A professional content manager may need a little help as they learn the ropes, but after a while, they typically catch on to any unique quirks in the system and are able to train any new folks that come after them.
In my past experience, the most difficult CMS hand-offs have been to organizations where the content is managed by a rotating group of volunteers or interns. This may be their first experience working with any kind of content management, and the temporary nature of the position means that it’s difficult to create any kind of lasting knowledge. If an issue comes up once, you bet it’ll probably turn up again in a few months once there is a new person in that position. My experience in handing over sites in these conditions has taught me that I need to actually consider who will be maintaining the site’s content, and not just treat them as an afterthought. A website isn’t a one-size-fits-all solution, so why would a CMS be?
Below is a non-exhaustive list of things I came up with when considering the editing experience. If you have any other tips, I’d love to hear from you!
Some of of the tips I described fall under good usability and cognitive accessibility. In an ideal world, it would be desirable to make sure every project you work on is robust as possible, but budgets and deadlines often pose a real challenge. It is a sad reality that projects and organizations that need this work the most often have the least amount of resources, as it’s not uncommon for things like community groups or non-profit organizations to be entirely staffed with volunteers. The pay for this kind of project may be heavily discounted, or non-existent. When working on a project for free/a discounted rate, I used find myself wondering if I could cut a few corners on the CMS side in order to make things more manageable, but taking this shortcut always came back to bite me in the form of future stress. Keeping the future editor’s capabilities in mind can make this kind of work more sustainable for you and the organization.
]]>CSS is a styling language that allows us to apply styles to our website. Although the browser includes some styles by default, we as web authors often choose to override those browser styles. On top of that, end-users can also choose to override the author styles with their own custom styles. With all these conflicting sources of styles, how does the browser determine what style should be applied? It comes down to two primary factors, the cascade, AKA what order your styles are in, and specificity, AKA how precise the styles are. In this article, we'll be focusing on the idea of CSS specificity.
.selector {
color: red; /* This is a declaration */
}
A CSS rule consists of a selector and a declaration. The selector is a way of determining what elements the following declaration is applied to. Imagine that you are a teacher, with an auditorium full of students in front of you. A selector is a way of addressing particular students so that you can give instructions (AKA declarations) to them. The general rule is, the more specific your instruction, the higher its specificity. In CSS, more specific selectors win out over less specific selectors.
Element selectors are a way of targeting elements based on what the element is. In our school auditorium metaphor, this might be equivalent to addressing all students from a particular grade. If you say "Fifth-grade students, stand up", you are addressing students based on what they are. An example in CSS might look like this snippet below, where we are grabbing all our anchor links based on the fact that they are anchor links. This represents the lowest level of CSS specificity.
a {
color: blue;
}
Attribute selectors are a way of grouping elements together based on different shared attributes. For example, in our school auditorium, we might want to address students across different grade levels based on some attribute they share. In this situation we might say something like "Students with long hair, stand up," to address all students with long hair. We could apply our previous knowledge and combine element and attribute selectors into a more specific instruction, such as "Fourth-grade students with long hair, fifth-grade students with long hair, sixth-grade students with long hair, stand up," but this level of specificity isn't necessary, and is more verbose than our original instruction.
An attribute selector in CSS is enclosed in square brackets and looks like this:
[lang="en"] {
color: blue;
}
Attribute selectors fall under the second lowest tier of specificity.
You might think of a class name as a name that is assigned to an element. As individuals, we probably like to think that our given names are unique, but in practice that is rarely the case (I should know, I'm almost never the only Nic/Nick on a team)! Depending on the name, it might be that your name only appears once in a group, but it may be used to address multiple students at the same time, eg. "Students named John, stand up" is likely to result in multiple students standing up.
We can write our class names in a way to target one or more elements, depending on the wider context of our site and what we as CSS authors might need. A class name like "footer" may only be needed once per page, but a class name like "primary-button" may appear many times. The class selector begins with a period and is followed by the class name. Class selectors fall under the second lowest tier of specificity along with attribute selectors.
.navigation {
color: blue;
}
So what if we want to grab a single student? The only truly unique identifier for an element is an id (technically, you can apply the same id to multiple elements and your page would still technically 'work', but this can have a negative impact on the accessibility and usability of your page!) An id can be thought of as a unique identifier for a student, such as a social security number or a passport number. Theoretically, there should only be one student that has a given id, so if we want to be sure that we're addressing a specific student, using their ID number is a more foolproof way to target them rather than saying something like "John Smith, stand up." An id selector begins with a hash, and is followed by the id. Id selectors fall under the second highest tier of specificity.
#navigation {
color: blue
}
Inline styles aren't technically a selector in CSS, but they represent the highest tier of specificity. We might think of inline styles as students who have headphones on during the school assembly. Regardless of what instructions are given to them, they have their own set of styles that they are listening to. A room full of students with headphones on is much harder to wrangle than a room full of students listening attentively, so it is in your interest as a CSS author to minimize inline styles. Inline styles are directly applied to an element in the HTML.
<div style="color:red;">I'm red!</div>
Combinator selectors are a way of describing elements in relation to another. In our student metaphor, this might be akin to saying something like "Students who sit behind the first row of students", or "Amanda, the younger sibling of Sally from fifth-grade." Though both are ways of addressing students in relation to one another, one is more specific than the other. Combinators are powerful and can unlock a ton of useful functionality without having to come up with granular class names, but be warned as combinators add up the specificity of all selectors in that particular combination. Chaining too many combinators should be avoided.
p + p {
margin-top: 1rem;
}
!important
!important
provides a way for us to override all of these selectors that we have previously mentioned. We might think of !important
as an announcement blaring over the school's public address system, it takes precedence over what is being announced in the auditorium, regardless of how specific the selector is. "All students involved in lunchtime incident, please come to the principal's office" and "Student number 92374, please come to the principal's office" are equally capable of overriding our previous rules. Like inline styles, !important
is not a selector, it is applied to a declaration directly.
a {
color: red !important;
}
It's fairly common for newer developers to be overly specific when writing CSS. I often see developers combining and chaining together long selectors that may correspond to the HTML structure of their page. A selector like #navigation > ul > li.current > .button
will get you the results that you want, but it can be more difficult to understand. Even more problematic, a selector like that can be hard to extend, as you keep having to find new ways to make subsequent selectors even more specific than your previous selector. The selector is much like stating "Students in fifth-grade, of which the one named John that sits behind Sally with blue eyes." It's a lot to keep track of! You'll have an easier time with CSS if you take some time to really think about what you are trying to target and how specific that selector needs to be.
Despite all the hype surrounding the game, I didn't expect to fall down the Stardew Valley rabbit hole myself. After a period of too much work I was left with a repetitive strain injury (RSI). I stayed away from the game because I assumed the Stardew Valley experience, like most other games, was now locked out to me. Due to my RSI, I couldn't understand the appeal of a game that seemed to involvebe doing repetitive actions over and over again. I finally picked up the game in 2021, and I was so glad I did! I was late to the party, but I now count myself amongst the millions of loyal fans that Stardew Valley has around the world.
As I became acquainted with the game, I soon found myself exploring the world of unofficial Stardew Valley mods, patches that players can apply to the base game in order to change some part of the experience. I've previously written at length about how how disabled Tumblr users have developed ways to make the platform more accessible, and was curious to see if the Stardew Valley community was similar.
I am not a full-time assistive technology user, and can play Stardew Valley without much modification with the aid of my niche equipment setup. You might enjoy the following video reviews of the base Stardew Valley experience for a more exhaustive overview.
Onehandmostly walks through his experience with Stardew Valley as a one-handed gamer. He covers the existing accessibility settings extensively.
Darrel Bowles, a completely blind gamer, praises Stardew's use of subtle non-verbal audio cues. He plays Stardew Valley in multiplayer mode with a partner who helped to build pathing so that he may navigate the farm via sound, much like a blind person might use tactile paving in public spaces.
Those of you who are web developers might already be familiar with the Web Content Accessibility Guidelines (WCAG), a series of documents on how to make the web more usable for people with disabilities. For video games, there's a similar effort known as the Game Accessibility Guidelines, which I've referenced for this article. While websites and video games have major differences, they both often are interactive digital experiences, and there is some overlap between accessibility best practices for both mediums.
Despite being the product of a sole developer, Stardew Valley offers quite a few accessibility features out of the box. The level of customization for the overall user interface and controls is more granular than most other games, even those with larger budgets and teams behind them.
There are options to remap nearly every keybinding, and many mouse interactions can also be achieved via the keyboard. This is a welcome feature for those who have difficulty using a fine-tuned pointer device such as a mouse, or those who suffer from strain (like me!) using conventional mice. You can scale the user interface and the background independently up to 150% and 200% zoom, which is great for users with low vision or those who may need larger hitboxes in order to successfully complete an interaction. There is also an option to use the default hardware cursor, which is helpful for those who have customized their cursor to their needs (typically by increasing the size or changing the color).
In terms of sensory needs, Stardew Valley allows you to disable flashing lights during the occasional thunderstorm. Players have the ability to toggle certain sounds individually, allowing people to create their own custom sound experience based on their individual cognitive abilities, sensitivities and triggers. For example, blind gamers like Darrel Bowles may appreciate audio cues that would let them know that their action has been completed successfully, but gamers with certain phobias or triggers may appreciate the option to turn off particular sounds.
In terms of settings and configuration, Stardew Valley goes beyond most large-budget games, offering a wide variety of options that would allow you to configure the basic gaming experience. However, there aren't that many options to customize the actual game itself to make it more accessible, and this is where mods step in.
The vast majority of Stardew Valley mods are available through the Stardew Modding API (AKA SMAPI). SMAPI is an installable piece of software that allows users to load mods by placing them in a particular folder on the filesystem. Once SMAPI is installed, mods can be downloaded from a variety of places such as Nexus Mods, which hosts thousands of Stardew Valley mods alone. While some mods are merely cosmetic or offer additional content, a quick glance at the most popular Stardew Valley mods on Nexus Mods shows us that many others target specific accessibility needs.
I have linked mods here for illustrative purposes only; these are not meant as endorsements. I do not guarantee that any of the below mods will work with newer versions of Stardew Valley, or at all. Rather, I link to them to demonstrate that people do want—and deserve—accessible gaming experiences. Additionally, please note that sites like Nexus Mods tend to have some fairly significant accessibility issues themselves.
One of the things that made me personally wary of playing Stardew Valley was its bright color palette. While I'm sure plenty of players gravitate towards the default bright and cheerful graphics, many people experience eye fatigue or migraines when looking at a bright color palette. Luckily, there's a whole host of mods out there that offer alternative color palettes for the game maps, in every flavor imaginable. Options include, but are not limited to, the following:
A Wittily Named Recolor makes the whole palette significantly more muted
Starblue Valley makes everything a little more blue, an option for those who experience eye strain with too much yellow
Natural Recolor makes the color palette more natural looking
Vibrant Pastoral, despite its name, reduces the saturation on all outdoor maps
There are also color-related mods that explicitly target accessibility needs. Dark User Interface offers a (you guessed it!) dark mode for the UI, and can be used in combination with a map recolor. For those who like light mode but struggle with the default bright yellow-orange interface, there's a beautiful, on-brand white and brown alternative.
A fairly common complaint from Stardew Valley players on various forums is it can be difficult to tell the difference between different object states, especially when the difference is highlighted primarily through color (Different object states can signal important information to players, such as whether an object in the game can be interacted with.) Mods such as Highlight Empty Jars address this by adding a more noticeable speech bubble on top of empty jars, or changing the empty jars to a brighter red color. Similarly, colorblind players can often struggle to tell crops apart or have issues identifying when things are finished growing, as color in the base game is often used to indicate ripeness. The SDV Colorblind Mod offers a helpful alternative for those with red/green colorblindness.
Playing Stardew Valley requires a certain degree of memorization. There are items you have to hunt down, seasonal crops and NPCs that have a specific weekly schedule. For gamers without cognitive issues, learning these things could be regarded as part of the game's overall learning curve. It mirrors your growth as a rookie farmer, and your knowledge about the world grows as the seasons pass.
However, for those with cognitive and learning disabilities, it can be difficult to ever reach that level of familiarity. Having to repeatedly pause a game in order to go double check something on the wiki is not my personal idea of a great time. Lookup Anything basically offers an enhanced wiki experience within the game. It even provides contextual information, such as your current friendship level with NPCs and whether or not you've crafted an item before. Similarly, Ui Info Suite enhances the level of information present in the UI, making hidden game mechanics more visible and improving the information found in the default tooltips so that players don't need to rely so much on memorization.
By default, Stardew Valley's map is static and displays only a graphic image of the world map. NPC Map Locations adds the different non-playable characters (NPCs) to the map, with locations updating in real time as they move around. It even adds a tooltip that will let you know when the NPC will leave their current location, so that you know whether or not you have time to meet them there before they leave.
Mods that help with cognitive accessibility are some of the most popular Stardew Valley mods of all time, with download counts numbering in the millions. As Nexus Mods keeps track of unique downloads and total downloads separately, we notice something interesting. With a mod like NPC Map Locations, the unique downloads number is around 1.3 million, where the total download number is around 3.3 million, nearly three times as much. This suggests that many gamers consider these mods essential enough that they continue to return for updates to ensure compatibility. Even a single purpose mod such as Range Display (which lets you see how wide an area your sprinklers and scarecrows affect) has download numbers in the hundreds of thousands. Options to reduce the cognitive load on players are vital for cognitive accessibility and appreciated by a large number of gamers.
Another highly popular category of mods revolves around reducing repetitive actions in this game. A big part of the core gameplay loop involves repetitive actions. To plant a crop, the player needs to hoe the ground, place the seeds, and water the seeds every day, until the plant grows. Over time, a player is able to upgrade their tools to make these tasks much less repetitive, but players with conditions such as RSI or carpal tunnel syndrome may find this initial hurdle too difficult to overcome. To most players, an extra click is an inconvenience at worst, but to some, an extra click can make the game unplayable.
Many mods allow users to bypass more repetitive aspects of the game, an effective way to minimize strain. The Tractor Mod is a popular configurable mod that can drastically cut down the number of repetitive actions, with the added bonus of not breaking player immersion. A mod like this could be used in multiplayer mode to allow a disabled gamer to play on equal footing with abled players.
While early-game gameplay tends to revolve around growing crops for some initial cash, more advanced gameplay involves turning your produce into different kinds of processed goods. The Automate mod gives the player the option to remove repetitive tasks by putting a storage chest next to the machine the player wants to automate, saving the player the effort of checking on the machine. In an open-ended game like Stardew Valley where players have the choice between dozens of different activities such as farming, fishing, mining, decorating, or even romancing your favorite NPCs, mods to reduce repetition give players the control to play the game exactly how they'd like to play it.
Stardew Valley's gameplay has a few elements that are considered time sensitive. Every seven seconds that pass in real life equate to 10 minutes in the Stardew Valley world. Most players will find that they are initially limited more by their in-game avatar's “energy” than by the actual passage of time, but players who use assistive technology may need a little more time to complete tasks, or may not be able to complete certain kinds of tasks at all.
The Timespeed mod does what its name implies: it gives the player full control over the passage of time in the game. You can slow down or pause the passage of time completely, and all of this can be tinkered with on the fly using various hotkeys, or based on personalized configuration for different locations.
Most actions in the game are not time sensitive, in the sense that the timing of your clicks and keypresses doesn't affect your success at the actual mechanic. There are two main exceptions—combat and fishing—where your ability to kill a monster or catch a fish is dependent on activating a control at a certain pace. There are many reasons why disabled players may not be able to achieve this, whether because a player has a condition that limits their ability to time their actions, or because the assistive technology they are using (an example might be a sip and puff switch) doesn't allow for precise timing. Mods like Fishing Made Easy, Skip Fishing Minigame and Combat Made Easy give the player granular control over different factors that make these aspects of the game challenging, allowing people to tailor their experience to their specific access needs.
At this point, you might be thinking, “But Nic, these mods have general appeal outside of accessibility needs. How do we know that these mods are actually benefiting disabled people?” Disabled people are loud and clear about how these mods solve various accessibility needs! Reddit user baibeach91 describes how mods allowed them to play the game after brain cancer left them with neurological difficulties, explicitly mentioning Fishing Made Easy as one of the mods that helps them. The creator of the mod responds to their post in the replies below:
It makes me beyond happy to hear the mod I created has helped make the game easier for you and others that are physically unable to play the game as it is.
A little backstory!
I actually created Fishing Made Easy Suite in early 2019 only because I have a friend that has a form of Parkinson's disease and was unable to catch the tougher fish.
Mods like these are a part of the curb-cut effect, where a feature intended for disabled people also provides benefits to a larger group than who the feature was intended for. The mod was originally created for a friend with Parkinson's, but many other people are able to enjoy the game to a greater extent because the mod exists. In many cases, accessibility features have the incidental benefit of making the game better for a large number of people.
That being said, much has been written about the curb-cut effect and the dangers of centering non-disabled people in discussions about accessibility—too much to put in this article. I'd like to specifically note that disabled folks are worthy of being considered, regardless of whether or not their accommodations benefit the larger population.
This final section covers a class of mods that are less likely to fall under the curb-cut effect: those that are specifically for a disabled audience. These mods seek to either integrate with existing assistive technologies such as screen readers, or to provide a Stardew Valley-specific version of an assistive technology. Although these mods may have lower download numbers, many have a far greater impact on people's ability to play Stardew Valley. These mods are often the only thing allowing the disabled users who download them to play the game, which would otherwise be completely inaccessible to them.
First up, we have Pelican TTS. TTS stands for Text-to-Speech, a popular method for blind and low-vision people to interact with text interfaces. This video shows off the gist of the Pelican TTS mod, though many features have since been added that allow players to customize voices for each NPC.
In a similar vein, StardewSpeak is a mod that allows for people to play Stardew Valley solely with their voice. The mod creator has put a lot of thought into how to make the player's experience as streamlined as possible, adding in a bunch of helpful commands that make this a viable way to play the game. For example, instead of forcing the player to count the number of tiles to reach the destination, the player can just say 'Go to farm', and the character will automatically find an appropriate path to the farm, taking all the twists and turns required.
Last but not least, we have the Stardew Access mod, which seeks to make Stardew Valley fully playable with just the keyboard (no mouse!), and adds support for popular screen readers such as JAWS and NVDA. The creator of this mod, Shoaib Khan, kindly agreed to speak with me for this article.
Shoaib is a university student who works on mods in his free time and moderates a Discord Server called Playability to gather feedback and collect bug reports. He got into modding when LogicProXGaming, a blind Minecraft streamer he followed, expressed a need for a mod that would tell him his current health on a keypress. Shoaib rose to the challenge and eventually began developing Stardew Access as well. When asked about the reception of Stardew Access, Shoaib mentioned that he never thought so many people would be interested in a mod like this, and that he's grateful for all the feedback that allows him to improve the mod.
This is where I believe that arguments against accommodations making games “too easy” or “for lazy people” fall flat. Imagine if you had to code an entire interface or submit a pull request just so you could play a game! Making games accessible is a tremendous effort that is unfairly borne by mostly disabled people themselves and by people like Shoaib. What a different world it would be if game developers considered accessibility from the start, and it wasn't something that players had to hack in themselves.
Many people feel strongly that allowing for users to change the difficulty of the game somehow compromises the experience or the “developer's vision”, to which I can only shrug. Being able to meet people where they are is a fundamental aspect of accessibility. Opening the door and allowing people to enjoy a game that they wouldn't otherwise have been able to play is a good thing. I can't believe this still needs to be said, but based on the comment section of any given article on gaming accessibility, disabled people continue to have to fight for this basic level of acceptance in gaming spaces.
User-created mods are not a replacement for first-party/developer support, for many reasons. In the case of Stardew Valley, mods only work on open platforms, not on closed platforms such as the Nintendo Switch or iOS, and many other games don't allow for the use of mods at all. What I want folks to take away is this: the fact that people would go out of their way to create tools to make the game accessible to themselves and others highlights the importance of creating accessible gaming experiences in the first place. I am only a visitor to the big wide world of gaming accessibility, and I hope that people will continue to support and follow the efforts of disabled gamers and their advocates.
]]>In all these years, one property I haven't been able to find a use case for is will-change
. To give a bit of an introduction to what will-change
is, Sara Soueidan wrote an excellent explainer which is well worth the read.
In short, if we imagine the browser as a large art or design program, using will-change
promotes that element to its own 'composite layer.' That layer can be hardware accelerated; that is, be rendered using the more powerful GPU (Graphics Processing Unit) instead of the default CPU (Central Processing Unit). Knowing that the element will change over time lets the browser isolate that element, and should improve rendering performance. Will-change
does come with the caveat that it should be used sparingly, only on elements that really need it. Offloading too many things to the GPU can slow down the page, the opposite of what we want.
With all that in mind, the will-change
property landed in major browsers in August 2015, and I've been on the lookout for when to use it ever since. It might seem self-evident to apply it to commonly animated properties such as transform or opacity, but the browser already classifies them as composite properties, thus, they are known as the few properties that you can already expect decent animation performance from. So, heeding the advice of the great developers who came before me, I was cautious and waited for the right opportunity to come along.
I hoped that will-change
would solve the Chromium bug where vertically centering a element using transform: translateY(-50%)
would result in a blurry element if the calculated height was an odd number of pixels, but our friend will-change
let me down there, it had no effect whatsoever. (I typically use Flexbox to avoid this issue now, as it sadly, still exists, seven years later).
Over time we even began to see articles critiquing will-change
appear. GreenSock, a popular JavaScript animation library, published a piece noting how the implementation of will-change
caused animations that had worked perfectly in the past to appear blurry, and the recommended solution (setting will-change: auto
, effectively, a reset) seemed contrary to the proposed behavior of will-change
.
In my mind, it was around this time where will-change
became something to be wary of. For the most part, I wasn't really writing animation code that was very complex, and could rely on well-tested libraries such as GreenSock or documented techniques such as FLIP for silky smooth animations. Browsers were only getting better at rendering performance, whenever I popped open Chrome's paint flashing tool to ensure that stuff was on its own layer when it needed to be, things were typically looking okay out of the box. I'd reach for it sometimes to try and fix the occasional rendering bug (usually as a cudgel after translateZ(0)
and backface-visibility: hidden
failed) but it certainly never was the silver bullet I hoped it would be.
But that all changed this week. I woke up one morning to find that a client had noted that the link animation was looking a little rough around the edges in Safari. Being a Windows gal, I fired up the good ol' BrowserStack to take a look, and saw this. (Forgive the screencap quality, it is a recording from Browserstack after all!)
My first reaction was surprise. Wasn't scale supposed to be one of the safe properties? Nonetheless, I reached deep into my toolbox, for the cursed artifact that I had never reached before. I may have even held my breath a little as I typed will-change: transform
and waited for my browser to reload the page.
It worked! I dusted off my hands and continued on with my day, excited to have finally found my white whale, a use for the will-change property.
But wait, there's more! I know I can't leave you all without the juicy details. What went wrong, and why was will-change able to help? In this particular codebase, we had a link, and a pseudo element is animating from a horizontal scale of 0 to 1 when the link is hovered (snippet below lightly modified for brevity). I tried dropping everything into a Codepen to see if I could reproduce it, but interestingly enough, it works fine without will-change in the default Codepen view, and shows up only in the debug view, the only view on Codepen without their wrapping code (which I cannot share).
.link::before {
content: '';
display: block;
position: absolute;
transform: scaleX(0)
transform-origin: left-center;
}
.link:hover::before {
transform: scaleX(1);
}
In a pre-pandemic life, I might have tried harder to get the bottom of this mystery, but alas, it was everything I had in me to type up this blog post, which I've uncharacteristically hammered out in a single sitting. Perhaps I will never know why will-change
was able to save me in this case. I'd be extremely eager to hear if other folks have had experience with the will-change
property smoothing any rendering issues, my inbox is open!
PS: Happy new year of the tiger. May 2022 be kinder to us all.
Since publishing this article, I've found some other folks who have had luck with will-change resolving rendering issues. Thanks to the folks below who allowed me to share their discoveries here.
Alex Riviere noticed that when using Chrome 98 with a Macbook Pro, the last cloned item in this infinite scrolling section would cause the last element not to render. Adding will-change: transform
fixes this.
Brian Cross noticed a recent issue with Safari 15 where GSAP transform animations that had worked correctly in previous versions were now leaving behind artifacts. The official GSAP team's response was to add will-change: transform
to the problematic element.
Justin Veiga came across an issue with Chrome on MacOS, where a photo enlarging animation done with transform: scale()
would display with noticeable jank. Adding will-change: transform
resolves this.
Read “A Complete Guide to Accessibility Tooling” on Smashing Magazine →
Unfortunately, default browser focus styles may not be all that accessible. In Firefox, the focus indicator is a 1 pixel wide dotted outline. In Safari, it shows up as a light blue ring. On sites where the brand color is a similar shade of blue, or even on a site where the background color is plain white, neither of these styles meet the required contrast ratio of 3:1 for non text elements.
Chrome pushed an update to their focus styles so that it displays both a solid dark color and a solid white ring. Although much improved, there are still certain cases where it may be difficult to perceive; for example, a black button on a white background would just appear as slightly wider.
As iffy as these styles may be for accessibility, you are technically not reponsible for the inaccessibilty of these styles. Your site can be fully compliant with the WCAG 2.1 (Web Content Accessibility Guidelines) yet have totally invisible focus styles, provided they are the ones provided by the browser. We really can do a lot better than that, and I would argue that anyone who cares about accessibility should go take that extra step rather than relying on inaccessible browser defaults. In any case, there are quite a few gotchas that often trip people up when designing and developing focus styles. Let's dive into some common issues with custom focus styles.
When I was just starting out, I often received designs with no interactive states at all! In many cases, it was on me to come up with hover and focus states. This led to me applying the same styling for all these states. While this is certainly better than having no focus states at all, the best focus states need to work in conjunction with other states. This is especially critical for things like checkboxes, where oftentimes a designer might design a hover or focus state that only works when the input is unchecked, but is incompatible with the checked/selected state.
By treating focus states as additive, you gain another benefit — on slower internet connections and devices, users may use the focus state as an indication that yes, the control has actually been pressed. On sites that are slow with high Time To Interactive metrics, I often find myself confused about whether or not a site is usable yet, and having an interface with carefully considered states may alleviate some of this confusion.
An 'affordance' is a feature or property of a design that suggests how it is supposed to be used. In web design, radio buttons and checkboxes are both UI patterns that allow a user to select options. Checkboxes allow for multiple items to be selected, and radio buttons offer a single choice amongst the options. While their exact appearance differs across browsers, default browser radio buttons are always round and checkboxes are square. Similarly, although focus styles differ between browsers, all of them are some form of outline, so it's best to design focus styles with this in mind.
I recently came across a case in the wild where designed focus styles that lack affordances were an issue for me. I've mocked up a recreation of the experience in the screenshots below.
This site had custom focus styles for their buttons, where the outline was removed and the background color of the button was changed. While the colors in question meet all contrast requirements, the button was not previously visible until the user scrolled, so it took me a bit of tabbing back and forth to realize what had changed. Users who rely on browser zoom may also encounter issues with this as well, as their viewport sizes may prevent them from seeing the previous unfocused state of the control. By relying on affordances and design conventions, we can ensure that the focus styles make sense out of context and that we aren't relying on the visual difference between the unfocused and focused state.
Designers often request that focus styles be removed because a thick ring that butts up against the control isn't the most visually appealing. For things like navigation links, you could consider adding padding to the link to ensure that there is some space between the outline and the content. This not only adds more breathing room to the design, but it also improves accessibility by increasing the tappable area of the control.
If tap target size isn't a concern or changing the padding isn't feasible, you could use the outline-offset CSS property, supported in all modern browsers. All you need to do is pass in an offset distance, such as outline-offset: 0.25rem;
, and the outline will be drawn the specified distance away from the element. Alternatively, you could utilize multiple box shadows by having a very thick box shadow in the color you would like the outline to be, and layer a box shadow the color of the background on top of it! (I learned this trick from Eric Bailey's very cool slides on focus styles.
button:focus {
box-shadow: 0 0 0 0.5rem white, /* background color here */
0 0 0 0.75rem blue; /* outline color here */
/* this results in a blue outline 0.25rem wide,
offset 0.5rem from the element, as pictured previously! */
}
Another reason why you may wish to offset the focus: if you have a element against a background where the contrast ratio just barely meets the requirements, it can be impossible to find a third color for the outline that also meets contrast requirements against all adjacent colors. By adding an offset, you only have to worry about the contrast between one color, the background.
overflow: hidden
Sometimes, even default outlines may be unintentionally removed when the parent of the interactable control has overflow: hidden
on it. This can happen on things like card user interfaces, where using overflow: hidden
to clip overflowing content will also remove the outlines. You could try consider using a negative value passed to outline offset, such as outline-offset: -.25rem;
or an inset box shadow (eg. box-shadow: inset 0 0 0 0.25rem red;
) in order to render an outline without changing the css on the parent element.
The following text about the outline not hugging the element is no longer true as of Firefox version 88, Chrome version 94 and Safari 16.4. If you need to support older browsers, considering follow the advice below, but otherwise, you're good to go!
Another reason why designers may request for the focus outline to be removed is because the default outline ignores rounded borders on elements. Thankfully, we aren't actually limited to using the outline property for custom focus styles! This issue can be solved by using box-shadow instead of outline to account for the border radius.
outline: none
, even if you override focus stylesCertain CSS properties are overriden in Windows High Contrast Mode, including borders, background colors and box shadows. A lot of custom focus styles are reliant on using these three properties and hiding the outline. In order to support high default contrast mode, Sarah Higley advises that we don't remove the outline completely with outline: none;
. Instead, we can set the outline color to be transparent, so that the outline is invisible when not in high contrast mode, but overridden correctly in high contrast mode, where the transparent color will be replaced with a color set by the user. You could preserve the existing outline styles with outline-color: transparent;
, or define a width and style with outline: 0.25rem solid transparent;
.
Focus styles are definitely something that should be considered from the beginning of the design phase. Because consistency and convention are key to designing successful focus styles, adding focus styles may not take that much more time to design and implement, but would add a lot of value in terms of making the interface useable for disabled users.
I certainly don't think this piece is either the first or last piece of writing on the subject of focus styles (curse you all for stealing the good focus related puns!), but hopefully it gets the gears turning in your head for possible focus style options. I invite you to read up on the articles featured within this post and below, and, as always, test with users!
Hello everyone! My name is Nic Chan and I have been contributing to the Maps 4 HTML Community Group over the past year, with a particular focus on the Uses Cases and Requirements report. Today I will be presenting on the accessibility of existing map widgets and tools.
Before I get into the details of the analysis, I’d like to lay out some definitions that will frame this discussion. The map tools discussed today fall into two general categories, widgets and client-side mapping APIs. Widgets refer to an embeddable map, typically in an iframe or something similar, that requires minimal code to implement. These include things like the Google Maps embed that small business owners may use to share their store location. Client-side Mapping APIs are JavaScript libraries that allow website developers to create more customized map viewers. They typically require the developer to do more configuration and instantiation work, with the benefit of offering more control over the end result. You would typically find these as a part of web apps like storefinders or delivery services.
In this presentation, I’d like to frame accessibility as the degree to which these maps are usable by disabled people. Some contexts may use the term accessibility to encompass other things, like cost and size of these tools and how that pertains to access, but in this case we will be focusing on the usability aspect, and especially how these mapping tools measure up to our next term, WCAG.
WCAG, short for Web Content Accessibility Guidelines, is a series of guidelines developed by the W3C in order to provide a shared standard for developing accessible web content. It is important to note that the WCAG is not the be all and end all of accessibility. When it comes to things like maps, a tool could meet every single criterion, but still not be accessible. There are many outstanding questions (which will be discussed in greater detail by other presenters and panelists) about what would constitute an equivalent experience to what a map can communicate for low vision and blind users. However, it is still worthwhile to examine the existing tools with the WCAG in mind, so we can account for other disabled users beyond low vision and blind users, such as people who have motor impairments. By looking at these existing tools, we can determine any common patterns worth adopting for a native HTML map element.
Not all WCAG criterion are relevant to web maps, so I’ve pulled out some of the most applicable and interesting ones, where the map tools meet these criterion with varying degrees of success.
The tools reviewed today include both widgets and libraries from vendors like Google, Bing, Apple, MapBox and TomTom, as well as some open source solutions with OpenStreetMap tiles. For a full breakdown of how each tool measures up and the details of each criterion, please visit Robert Linder’s full evaluation at tinyurl.com/wcag-maps. Robert is a member of the Maps4HTML Community Group, and his contributions have been invaluable.
With that out of the way, let’s begin!
5/11 tools failed.
This criterion states that all non-text content has an equivalent text alternative. None of the map tools offer any meaningful text alternative to the map content by default, so on that front, all of the maps would fail, unless context is provided by the site developer. As mentioned earlier, what this alternative content could look like is a very complicated question that depends on the content of the map, and warrants larger discussion.
Because developers and authors may choose to provide additional context, when evaluating this criteria, we are focused on text alternatives to controls other than the map itself. In this case, this criterion also applies to things like alternative text for logo images and making sure all icon buttons are labelled descriptively for screen reader users.
As an example, maps often use the plus and minus symbol to indicate zooming in and out. As a sighted user, I can see the visual context these buttons are in, and I can also see that the map zooms in and out when I press the buttons. However, if you just hear “Plus Button”, the context is a little more unclear, it could mean adding a pin to the map, or changing the quantity of another control. We can include context without changing the visual appearance of the control by adding things like alt text to images, or an aria-label to the control. A possible solution to resolve this example might be to add an aria-label to the button.
11/11 tools failed.
This criterion requires that all information, including a control’s state or structure, can be determined programmatically or is otherwise available through text. Tools often failed to label the map’s structure as a distinct piece of content and as a control. A map generated by these tools is not just a static image, it is a control in and of itself. When the map has focus, most tools will allow you to do things like pan and rotate the map using keyboard shortcuts or your mouse pointer. Thus, the map needs to be labelled so that it is identifiable as a control, not just an image. In tool authors’ defense, there isn’t a set standard or consensus for how to approach this yet, as ‘map’ is not yet a valid component role, so there may be uncertainty on how to appropriately label this map container. The other most common reason for failure is the lack of attention paid to control state. For example, all of these web maps allow users to zoom in and out to an extent. Once you’ve hit the zoom limit, the appropriate control should be disabled, but very few examples communicate this programmatically, and most only adjust the styles. Furthermore, screen reader users will often have no idea of the current zoom level. Bing stands out amongst the examples as it announces the current zoom level in addition to the buttons state, so a user of an audio interface will always know what the current status is.
9/11 tools failed.
To pass this criterion, all text should pass a minimum contrast ratio of 4.5 for regular sized text. The contrast ratio refers to the contrast between the text color and the background color. Because many widgets do not allow for color customization, having accessible defaults would go a very long way in terms of ensuring map accessibility. This contrast criterion can apply to both the map controls and the actual map content. Some maps had text buttons that did not meet the contrast ratio. Fixing this is as simple as changing the text color on the buttons. Most maps had contrast issues within the map content, particularly with the labels for bodies of water and minor roads, such as in this example here. This screenshot shows the ocean filled in with a light blue color, but the text on top of it that says ‘North Atlantic Ocean’ is a medium blue, and it never gets any darker or larger no matter how much you zoom in. Maps are in a tricky position as color hierarchy is very important for establishing focal points, and there is a need to deprioritize certain map features to avoid making the map too noisy. When a map is zoomed out, it is understandable if some minor labels are below the average font size, or have lacking color contrast. However, when a map is zoomed into an area, a good map may have these minor labels take visual priority and be displayed larger and with more contrast. Some maps included a thin white outline around each label to increase the text contrast, a technique that is commonly used in closed captions for TV.
7/11 tools failed.
Every functionality that is available to mouse or touch users must also be available for keyboard only users. This includes all interactable controls, as well as functions activated by pointer gestures, which most maps do have. Most failures in this instance are due to the use of non-native controls. When a semantic element like a HTML button is used, all the behaviors that are expected of the control are handled automatically by the browser. It is very common for developers to add a click handler to a div element and ignore the other required behavior. Some custom buttons are not able to be accessed by hitting the tab key and cannot be activated because they cannot be accessed at all, whereas others can be focused, but do not have any means of activation through the keyboard. A user with a pointer device can pan across the map by dragging the map interface. Some maps lack a way to do this with the keyboard, either because the map container is not focusable, or because they lack the keyboard shortcuts necessary.
1/11 tools failed.
This criterion states that if a keyboard shortcut is implemented using character keys, then at least one of the following is true: A mechanism is available to turn the shortcut off; or A mechanism is available to remap the shortcut to use modifier keys such as shift, control, alt, or The keyboard shortcut is only active when that component has focus. This criterion exists to prevent conflicts with assistive technology shortcuts and web interface shortcuts. The NVDA screen reader uses arrow keys to navigate text, and it may conflict with the use of arrow keys to pan and scroll the map. None of the web maps we analyzed used techniques #1 or #2. The web maps that have shortcuts and passed this criterion have opted for technique 3, only allowing keyboard shortcuts when the map container has focus.
2/11 tools failed.
To pass this criterion, elements must receive focus in an order that matches the content reading order. In English and other left-to-right languages, we have an expectation that elements will be interactable from left to right, top to bottom, in the same order we read text.
Here is a diagram which displays the focus order of the Google Maps embed. The numbers indicate which element receives focus in what order. The first focusable element is the map container, which makes sense as the focus order should go from outward to inwards. Next is the Terms of Use button, which is in the bottom right hand corner. After that, comes the View Larger map button, which is in the opposite corner, in the top left. We return to both zoom control buttons in the bottom right, and finally, the unlabelled toggle satellite imagery button in the bottom left corner. In this case, the developers used styles to position the controls arbitrarily. Without proper focus order, the experience of tabbing through the map component is quite jarring and unnecessarily disorienting to bounce from corner to corner.
7/11 tools failed.
This criterion is related to the previous one. The focus ring is an indicator that shows which interactive element currently has focus, an example of which is shown here. We can see the zoom out button currently has focus due to the thick black ring that Chrome applies. It is present by default in all browsers, but is common for developers to remove this indicator without replacing it with their own styles. In combination with the previous criterion, this criterion is serious enough to render a map component unusable for sighted keyboard-only users, because there is no way for them to determine what element is currently selected.
In some cases, the focus isn’t persistently visible, which is due to the addition of scripts that remove the outline once the element is focused. This criterion is an example of the default behavior being accessible, and the developers have overridden this default behavior to make an ultimately inaccessible design choice
9/11 tools failed.
The requirement to pass this criterion is fairly simple, all it requires is that the content’s language is included in the markup. This is important for many accessibility reasons, such as making sure screen readers read words with the correct intonation, and can be useful for features like on-the-fly language translation. Despite the fact this criterion is simple to achieve, many vendors fail to implement this. Passing this criterion is as simple as adding a single lang attribute to the containing element, and lang attributes to text in other languages.
The next slide has a gif which demonstrates an unexpected scrolling appearance. Viewers with vestibular issues may wish to look away for a second.
2/11 tools failed.
This criterion states that changing any setting in a user interface component should not trigger any changes of context unless the user has been warned about it. In layman’s terms, if a control is used, there shouldn’t be any unexpected side effects like changing the viewport size. Here we have a gif where the up and down arrow keys are being used to pan the map, but unfortunately the viewport scrolls up and down as well. This can be quite disorienting, so it’s important to test for side effects like this.
Building accessible web maps is no easy task. The base level of accessibility for existing web map solutions is quite poor overall, and no one widget or library stands out as being far superior to the rest. Criteria where almost every tool fails are often due to the lack of standardization, and this is where having a native HTML map element could really set the bar for what map widgets should strive to be.
Regardless, there are quite a few quick and easy wins to be had. I hope any developers who work with these map libraries can consider implementing some of the low-hanging fruit I’ve brought up here.
Thank you! If you have any questions about the material covered, feel free to reach out to me at nic@nicchan.me.
]]>As Shopify is popular with small business owners who can't afford a custom-built store, I began to wonder about the state of Shopify themes. What sort of options were out there for a merchant looking to purchase an existing theme from the theme store, and what could I safely recommend to merchants so they could have an accessible storefront from the beginning? I decided I would conduct some basic accessibility testing of all the themes on the homepage of the theme store to get an overview of what the theme landscape looked like.
For this article, I chose to test the themes for a specific task using two testing methods. First, I would try to add a product to my cart and reach checkout with my monitor obscured using only the NVDA screen reader on Chrome, and then I conducted the same test with the display unobscured but with the keyboard only, no mouse, trackpad or other pointer devices. This could be described as a simplified version of a task-oriented walkthrough.
Proper accessibility testing takes a long time. To be able to fully evaluate a website’s accessibility, it needs to be tested with actual users on a variety of browsers and assistive technology combinations, ideally against the WCAG 2.1. I only tested these themes on my own, for a single task, so take my findings with a grain of salt!
The list of themes consists of every single theme on the Shopify theme store home page, pulled from some time in April. 32 themes were tested in total, and testing was completed sometime in May, so bear in mind that some themes may have been updated since then, especially with increased discussions about the Domino’s Supreme Court case.
Where a theme had more than one color preset, I opted to test the first one. Most themes have 3-4 presets and the difference between them usually consists of different configurations of fonts and colors. While I certainly think that the choice of fonts and colors can be very revealing in terms of demonstrating a developer's commitment towards accessibility, almost all themes allow the merchant to change the site's fonts, font sizes and color palettes; thus merchants are able to improve these kinds of inaccessible theme defaults. As a result, I did not conduct any automated testing for things like color contrast or font sizes on these demo stores, and I only focused on whether or not I could complete the task at hand.
If at any point, I lost track of focus for more than a few tab stops, I would consider the keyboard test a fail. Your users will not have enough patience to debug your website's problems, even if I personally continued trying in order to determine further issues with the theme.
When asked why they do not bother to make accessible websites, developers often complain that it is too difficult to make things accessible because of the many niche bugs that occur with particular screen reader and browser combinations. In my experience, this couldn’t be further from the truth. Of all the failures I encountered, not one was due to an edge case scenario with my browser/screen reader combination, and the vast majority of errors came from ‘low-hanging fruit’. These kinds of errors are not difficult to fix. Semantic HTML and appropriate use of CSS can resolve most of these issues without complicated hacks or dependencies. Below is a summary of the most common reason why themes failed, with some resources that can help you learn how to resolve these common issues.
The vast majority of navigation failures for the keyboard only test were due to the fact I lost track of where the keyboard focus was immediately. The solution to this is as simple as not adding *:focus { outline: none; }
to your code in order to display the default focus outline, but a nicely designed one can often be an easier sell in terms of clients wanting to retain a certain brand aesthetic, and be more visible than default browser options.
Many e-commerce sites contain menus that are displayed when the parent link is hovered. Not only do many themes neglect to show the menu on keyboard focus, they also neglect to consider the usability of this pattern for tablet users. Developers should consider whether or not a disclosure widget style navigation is more appropriate in this situation. If other stakeholders insist on showing the menu on hover as opposed to click, consider using the :focus-within selector in order to allow keyboard-only users to at least be able to navigate the menus. (Disclaimer: if you have more than a few links in your dropdowns, definitely consider the disclosure widget pattern, as it may constitute a failure of 2.4.1 Bypass Blocks.
A lot of themes rely on content that may be hidden initially to the user, and displayed upon interaction, such as dropdown menus, quickshop modals, and etc. These UI patterns are frequently animated in from their initial hidden state, with developers utilizing properties like opacity and transforms. Although changing these properties may hide content from a sighted user, it is important to also also ensure that this hidden content is also hidden for screen reader users and keyboard only users. This is a great summary of various approaches, but the gist is that visibility: hidden
respects CSS transitions, and can be used in conjunction with the animateable CSS properties in order to hide content for all users.
During my testing there were many times where my keyboard focus got stuck in an off-screen hamburger menu, and I was forced to give up.
Icons in interfaces are becoming increasingly popular, but unless they are clearly labelled for screen reader users, they can be extremely confusing to navigate. Many times, icon buttons came in groups (eg. social media links), so I was left trying to make sense of what “Clickable Clickable Clickable” meant. If you are implementing icon buttons or links, you can label them for screen reader users without changing the design, but bear in mind that icons are likely not as clear as you think they are.
There were several themes where the well-meaning developer had attempted to use ARIA roles to supplement their markup. Unfortunately, ARIA is often not well-understood by developers, and incorrect use of ARIA can lead to mismatched user expectations. One theme had a mega menu where every link was marked up as a role="menuitem"
within several levels of role="menu"
s, resulting in every link being announced as ‘menu item submenu menu menu item submenu link [LINK NAME]’. This is extremely verbose, and an incorrect use of the ‘menu’ and ‘menuitem’ roles. If you aren’t 100% sure what ARIA is doing for your markup, it’s best to leave it out completely rather than botching what would have been an accessible experience. The HTML spec has recently been updated to include a new ‘Accessibility Considerations’ section that can help you determine whether or not a certain role is applicable in your situation.
Many merchants sell products that have multiple ‘variants’. Different options like sizes or colors are presented as form options that a customer can toggle between. Developers often try to implement custom controls for these form options in order to maintain complete control over the way these form options look, but these attempts can cause accessibility issues in several ways. Below is a list of some of the ways in which these form controls failed:
<select>
elements that resembled modal dialogs did not move focus appropriately or have keyboard interactionsBy implementing custom controls, developers lose a ton of value offered by using proper form controls. Because the form state and submission process are dealt within proprietary JavaScript, instead of within the form itself, developers not only create additional work for themselves on the accessibility front, they also make it extremely difficult to introduce third party code (such as from apps or external developers) that can extend the functionality of a theme. This results in a theme that is more difficult to maintain, less flexible for merchants and a poor experience for disabled users.
Styling form controls have come a long way; a lot more is possible now, with strong consistency across browsers. Instead of potentially botching your own attempt at a custom form control, consider referencing examples of styled form components and incorporating them into your work. For Shopify product variants in particular, a lot of common UI patterns like color swatches can be represented by radio buttons because of their ability to select a single option among a group.
AJAX cart interactions are extremely common in e-commerce these days, as they allow the customer to complete actions without having to wait for the page to reload. Unfortunately, far too many sites do nothing to manage the keyboard states. In the rare instance where I was able to reach the Add to Cart button, I would often hit the button excitedly only to find that nothing at all would appear to happen. What an anticlimactic experience! Sighted users would be able to see either a notification window or an AJAX cart modal/drawer pop up, but if the focus is not moved, screen readers experience no indication that anything has happened.
AJAX interactions aren’t the only place where we encounter issues like these. Many modern user interfaces tend to include complex interaction patterns such as modals, but many implementations will just toggle the visibility of the modal and fail to follow any of the guidelines for keyboard interaction. If they’re lucky, a keyboard only or screen reader user may eventually be able to tab their way into the target component, but you would be lucky to hold their patience for that long.
Horizontal headers represent the developer of the theme, and vertical headers represent where in the testing process the first accessibility failure took place. The later on in the process, the “better” a theme is, but given that the process of purchasing an item is integral to an e-commerce store, anything below a “pass” should be unacceptable.
Shopify | Third-Party | |
---|---|---|
Navigation Failure | 0 | 11 (42.3%) |
Product Failure | 0 | 4 (15.4%) |
Add To Cart Failure | 1 (16.7%) | 6 (23.1%) |
Pass | 5 (83.3%) | 5 (19.2%) |
Shopify | Third-Party | |
---|---|---|
Navigation Failure | 0 | 9 (34.6%) |
Product Failure | 0 | 2 (7.7%) |
Add To Cart Failure | 3 (50%) | 11 (42.3%) |
Pass | 3 (50%) | 4 (15.3%) |
I didn’t want to critique individual theme developers with this exercise, but I did notice that themes built by Shopify performed noticeably better than than third-party themes. While many larger third-party developers had frequently asked questions or pages on their websites about accessibility, these pages often had expressed superficial commitment, and placed the burden on disabled people to report all errors, without making any effort to make these pages themselves accessible. You can see what a predicament this places disabled people in, and it allows accessibility to be deprioritized by false statistics (ie. “We haven’t had any complaints about this!”)
On the other hand, Shopify’s accessibility team is small but mighty. Scott Vinkle and Devon Persing are both wonderful people who are committed to advocating for accessibility beyond their roles at Shopify. The codebases at Shopify are an order of magnitude larger than those by the average theme developer, and the fact that Shopify still came ahead in these tests is a testament to both their hard work and Shopify’s increasing commitment to accessibility.
It’s vital for Shopify to continue to model best practices. When reviewing the source code for the themes, I observed that many theme developers would often copy the markup and JavaScript for complex components from a Shopify theme to use as a functional starting point for their themes. Because of this, Shopify’s theme accessibility efforts not only benefit stores that use Shopify’s own themes, but also, the themes built by developers who seek to emulate Shopify’s work.
Theme developers should not regard Shopify’s relative successes in accessibility as a case of ‘let’s bring on a single person to do all of the accessibility for us!” It should never be the burden of a single individual to ensure accessibility. Company-wide buy-in is necessary not only because it takes only one small error to render a site unusable, but more importantly, the people who do work that falls under the umbrella of ‘Diversity and Inclusion’ should not be placed in a position where they are seen as constantly rejecting and critiquing the work of other people in the company. When we move beyond this idea of just ‘compliance’ and integrate accessibility holistically into the entire process of building things, we might finally be able to move the field forward.
Finally, let’s revisit the initial question. What sort of options are out there for a merchant who is looking for an accessible theme? Scott tells me that so far, Debut, the theme that all stores start with, has undergone rigorous user testing and is updated to meet WCAG 2.1. Other themes by Shopify will undergo the same process eventually, but I’ve already noticed improvements in the time it’s taken me to write this article. Sometime in the future, baseline accessibility requirements may be imposed on all themes and apps within the ecosystem, so it’s wise to start thinking about accessibility now. Until this day comes, the options are fairly limited if a merchant wants to pick a WCAG 2.1 compliant theme - either use Debut out of the box, have a developer experienced with accessibility customize it to your needs, or opt for a custom theme. I hope that more developers in this space will see the need for options, and rise up to the challenge.
]]>When I was a child, my understanding of disability was very binary and limited. You were either disabled or not disabled; my younger self might imagine someone in a wheelchair, or someone with a prosthetic limb. My understanding changed entirely when I met Maddy Ruvolo, a disability activist whom I had the pleasure of meeting in college. Maddy was the co-founder and president of the school's Disability, Illness, and Difference Alliance, and she wrote her senior thesis on chronically ill youth and the role that Tumblr played as a model of disability community.
Because I am the kind of big nerd who enjoys reading her friends' theses, it was through her and the many subsequent hours I spent browsing Tumblr that I began to reevaluate my limited, abled conception of disability. It may be tempting to try to categorize experiences of disability into three distinct categories; [1] what we might call more visibly disabled people, [2] chronically ill people, and [3] mentally ill people. Over time, I came to understand that while there may be significant differences between these three experiences, there is also significant overlap among their access needs and struggles. The boundaries between these three categories are so blurry that we cannot afford to make generalizations about what we perceive these groups to be.
Tumblr, a microblogging platform popular amongst young people, was always a place where niche communities could flourish, and discussion of topics such as race, sexuality and gender was frequent and widely shared. Among these communities were those that discussed various aspects of disability, particularly chronic illness and mental illness.
Unfortunately, Tumblr's own terrible web accessibility has excluded a very large portion of the disability community, primarily by being completely unusable to those who use assistive technologies such as screen readers and keyboard only users. As a result, the discussion surrounding disability on Tumblr has been shaped by the absence of disabled people who were more likely to be experts in the technical facets of web accessibility. While I think that Tumblr (and all platforms) should commit to complying with the Web Content Accessibility Guidelines (WCAG), there are still valuable lessons to be learned by examining the idea of accessibility from different, non-technical perspectives.
Often maligned as 'the blue hellsite' by its own user base, Tumblr as a platform has always had a hostile relationship with the majority of its users. Tumblr users reading this post may remember the time they removed the ability for users to publicly reply to each other’s posts. Tumblr’s vague official announcement post did not go over well, and users were left to speculate whether the new instant message/direct message (DM) feature was really going to replace the ability to reply. Imagine if Twitter removed the ability to respond to tweets and insisted that DMs were a replacement! Although Tumblr did eventually bring replies back, this was just one incident in a long series of disappointing management decisions from Tumblr. Most recently, Tumblr’s mass purging of ‘adult content’ has de-platformed many queer creators, regardless of whether or not their content actually contained ‘adult content’, further contributing to the toxic idea that queer people are inherently inappropriate for children.
Long-time Tumblr users may also recall using Tumblr-specific extensions in order to improve their browsing experience. Tumblr Savior was among these extensions, and for many users, it lived up to its name and made Tumblr into a usable experience. Originally released in 2008, Tumblr Savior functioned similarly to Twitter's muted words, though it was even more powerful because it searched the full contents of the HTML when deciding whether or not to hide a post. This muting mechanism was helpful as instead of relying on the poster to tag posts with specific tags, Tumblr Savior would hide anything that mentioned the offending topic within the body of the post.
Most abled users would use this blocking feature to hide things like TV show spoilers or mentions of a fandom they didn't particularly care for, but many disabled people considered Tumblr Savior a necessary tool in order to use the platform at all. For example, people with motion sensitivities or conditions like epilepsy could choose to universally block all gifs in order to avoid potential triggers. Similarly, people who experience trauma/Post Traumatic Stress Disorder (PTSD) or certain phobias could avoid topics which may induce panic attacks and other negative responses.
Muting mechanisms and preference controls are vital tools for accessibility. Platforms that host content, whether they be social media networks, news outlets or something else entirely, have an obligation to allow users to control their own experiences. Whether it be someone hoping to browse Instagram without triggering their eating disorder, someone trying to keep up with their relatives without being subject to a seizure-inducing gif, someone avoiding the details of a recent celebrity suicide for their own mental health or someone with a serious phobia of balloons, we all have a right to control what content we consume. Platforms do not have to take it upon themselves to block or censor any content that may negatively affect users, but providing a way for users to opt out of content is absolutely an accessibility issue, especially when marginalized communities, who experience trauma and anxiety at higher rates, continue to be the targets of hate crimes and political attacks, both online and offline.
Third-party Tumblr apps once provided a number of other features that allow disabled users to overcome Tumblr’s shortcomings. Missing E and XKit (both of the original apps are now defunct, though XKit lives on with a new team behind it, they're always looking for contributors) were two of the most popular more full-featured extensions. In addition to providing muting and advanced blocking implementations for better content filtering and harassment prevention, these apps contained features such as one click reblogging and auto-tagging. One-click reblogging allowed for users to share a post to their own blog very quickly, and auto-tagging would allow users to apply appropriate tags to a post with minimal hassle. In her thesis, Maddy argued that because users with chronic illnesses often experience fatigue issues, features like one-click reblogging enabled people who may be too exhausted to leave the house to engage in some form of social interaction, however small, while still accommodating for their followers' triggers by automatically tagging posts when necessary. As abled people, we often regard features like this as a convenience for most users, but they can be necessary for disabled users to experience the app at all.
You might think that Tumblr would look favorably upon the developers who spent countless unpaid hours working to make their platform more usable, but instead, Tumblr actively waged war with third-party app developers. In 2011, users with the Missing E extension installed were greeted with a pop-up that warned against using a 'browser hack', which Tumblr claimed could lead to disastrous consequences such as data loss. Tumblr ultimately banned Missing E, despite the developer's attempts to work within the guidelines that Tumblr had set.
In 2017, half a decade later, Tumblr finally released a post filtering feature. In comparison to some of the third-party extensions, Tumblr's solution only enabled users to filter by tags. Relying on users to tag things consistently is inferior to full text search (imagine if Twitter’s muted words only worked for hashtags). The more comprehensive Chrome extension Tumblr Savior still has over 150,000 installs today despite the fact it no longer works consistently and was last updated over a year ago.
Generally speaking, platforms like Tumblr are often hostile to third-party developers, and by proxy, disabled users. This can manifest in ways that are overt, as with Tumblr's active attempts to block third party extensions, or in more subtle ways, such as when a platform makes engineering decisions in order to achieve some goal like improving developer convenience or thwarting adblockers, but with negative consequences for accessibility.
Facebook's 'Sponsored' link is a particularly egregious example of this. The sponsored link is a label on a post to indicate the content is sponsored. In order to display the word 'Sponsored' without directly displaying the word in its entirety, for fear of allowing detection by ad blockers, Facebook added dozens of extraneous letters in nested tags in hopes of making it difficult for ad blockers to parse the text. Until recently, the redundant code also made it difficult for assistive technologies to parse the text, resulting in some screen readers reading out 'S-P-O...' etc. as individual letters. After several months, Facebook appears to have resolved this (at least for the NVDA screen reader on Chrome), a minor victory.
Keeping things simple and semantic is key to writing good code. 'Semantic' in a web development context generally refers to using the right kind of HTML element for the job. Using the proper elements in the right context provides affordances for things like search engines and assistive technology, allowing them to convey meaning to the end user. In Facebook's case, their 50+ line monstrosity can be replaced with a single tag, <span>Sponsored</span>
. Overengineered solutions can make things inaccessible to users and dramatically increase the size of the page, slowing down the website and using up more data.
In addition to writing bloated, unsemantic code in order to thwart adblockers, Facebook’s code architecture decisions made it difficult for third-party developers to create accessibility fixes. For example, Facebook uses a CSS-in-JS solution that generates class names. CSS is a styling language that changes the appearance of elements on the page. We typically attach these styles through class names, so writing something like .box { background-color: red }
would make all elements with the class name 'box' have a red background.
Third-party developers often rely on consistent class names in order to create extensions that last beyond a single update, but Facebook has prioritized their engineering needs over their end-users. Creating a scalable, easy-to-use naming system is a difficult task, as applications like Facebook have several thousand unique components that must be named in order to prevent styles from unintentionally affecting other parts of the page. Many CSS-in-JS solutions, including the one Facebook has chosen, take care of the naming process for you, and thus instead of having names like ‘box’, we get non-descriptive names like 'q_q25znkklu', as found in the screenshot above. While it is not necessary for class names to be meaningfully named, generated class names are often regenerated when changes are made. Any third party extensions that rely on those class names will have to be updated to accommodate the change, even if the actual styles themselves do not need to be updated. The most popular themes for Facebook are updated several times a week in order to keep up with Facebook's changes. Maintaining a theme for one of these applications is a thankless and time-consuming task, and many developers are forced to give up.
Although changing one's theme to feature Game of Thrones characters is likely not done to improve accessibility, many user-developed custom stylesheets are designed for accessibility purposes. Dark themes can reduce eye fatigue and headaches for people who have to look at their screens for a long period of time, and can be especially helpful for those with light sensitivities and or those who experience migraines. Fonts can be changed to better support readers who require larger fonts or fonts with better contrast (such as for dyslexic users), and user interfaces can have distracting (animated or otherwise) elements removed in order to help people with ADHD or other sensory disorders better focus on the task at hand.
As web developers, our first impulse might be to try and distill all these experiences into an actionable list. As tempting as it is to treat accessibility as a checklist, it does not work that way. As humans, we are complex and our access needs may conflict with one another. For example, I personally prefer dark themes and have a screen color temperature adjuster on at all times in order to manage the eye fatigue and headaches that often happen when you spend all of your time staring at a screen. However, some people experience glare, blurriness or streakiness when looking at light text against dark backgrounds, and dark themes would therefore be unusable for them. Even in the rare case that a platform provides both dark and light theme options, the size of a platform like Tumblr guarantees that some people will have conflicting access needs. People with multiple disabilities often have to make sacrifices by choosing between features that may support them in one area, but worsen another.
I don’t believe that developers for large platforms like Facebook and Tumblr are sitting in a room discussing how to make their sites as inaccessible as possible. They face attacks from a variety of bad actors, and their ever-changing, complicated codebases may be part of an active attempt to thwart these attacks. However, we cannot let our desire to fend off potentially malicious technology override the needs of actual users. When we make websites difficult for bots and scrapers to use, we have the unintentional side-effect of making website difficult for assistive technologies to use. Tools such as screen readers and magnification software are widely classified as assistive technology, but custom extensions and stylesheets should also fall under that category. If you can't build something that accommodates the needs of users with disabilities, then the very least you can do is to build it in a transparent way that allows for them and their advocates to fix it on their own. When a platform is engineered for clicks and engagement, we prioritize profits over the people we are trying to serve, ultimately leading to unethical design practices. In short, the web is not a compile target and we should not treat it like one. Our users are not our enemies, and it should not be acceptable to exclude people with disabilities for profit or developer convenience.
To recap, Tumblr as a platform has blatantly ignored the requests of it's disabled users, and was hostile to third-party developers that made it's platform usable. While Tumblr as a platform has very much served as a lesson for what not to do, Tumblr's disability community has developed unique strategies in order to cope with the inaccessibility of the platform, modelling what we might now call 'inclusive design' before the term was coined.
For those who might not know, individual Tumblr blogs live at their own URL, and can be styled using different themes, much like a MySpace page or a Wordpress blog. So while the Tumblr dashboard and application that bloggers use to make sites may not be compatible with most assistive technologies, individual blog owners can make their rendered sites somewhat more accessible through choosing more accessible themes. (I quantify 'somewhat' here as Tumblr has made many baffling decisions that make full compliance with the WCAG almost impossible).
Tumblr's theme store offers a wide variety of themes, ranging from paid premium themes made by professionals and agencies to themes made by hobbyists who learned how to code by copy-and-pasting snippets from each other. In terms of accessibility, there is almost no correlation between the cost of the theme and how accessible it is. Sure, you have many themes built by hobbyists that are using 12px white fonts on pastel pink backgrounds, but it is painfully common for websites built by professionals to not be accessible at all. It is much easier for a simple theme built by a passionate hobbyist to be built accessibly by virtue of not adding the bloat and fanciness we are used to seeing in web projects these days.
Within the Tumblr theme building community, which is largely populated by non-professionals, certain practices have been created for accessibility purposes. For example, many themes offer the ability to display a spoon meter (derived from Spoon theory, a popular disability metaphor) or other variants such as stylized health meters and battery indicators. In a world where social media places immense pressure on us to be constantly available and performing our best selves, there is power to be found in being vulnerable and disengaging from the demands that social media places on our time. Declaring 'I am low on spoons' and having that be respected and accommodated by one's followers is a profound action, and the ability to set boundaries is necessary in order to allow disabled people to participate in online communities.
Tumblr theme developers have often repurposed certain platform features in order to improve accessibility. An interesting example of this is the {TagsAsClasses}
functionality. The {TagsAsClasses}
feature outputs tags on a post as CSS classes that can be referenced for styling purposes. Although this feature was likely created for more commercial purposes, users have utilized this tag for a variety of access needs. Users can add support for multilingual blogs by tagging posts with right-to-left languages (such as Arabic) appropriately. It can also be used to allow users to accommodate for their followers' needs by tagging things for common phobias or triggers such as #motion warning, #no captions, #spiders, #gore, and hiding that content or placing it under a ‘Read More’ cut.
Several themes offer a wide variety of settings in offer to support as many different kinds of reading experiences as possible. Amateur theme developers share snippets for light and dark mode toggles, bold/italic style removers and font family switchers (to toggle between fonts that users may find more readable, as people have different preferences). While their implementations aren’t always perfect, unpaid amateurs, many of whom are students, are putting in massive amounts of effort to improve accessibility. We, as professional developers, should take pause, as we often fail to do put in even minimal effort. We absolutely need to do better.
One final practice that I would like to mention is the idea of 'Community accessibility' as it is practiced on Tumblr. In this case, 'community accessibility' refers to the practice of individuals contributing their own time and ability to improve accessibility when the original creator is unable to. For example, someone with fatigue issues may need to create a long post but be unable to type. They can record a video of themselves and tag the post #accessibility request, in the hopes that one of their followers will add transcripts or captions to the post, which can then be shared with the video.
Another common practice is to add image descriptions to images, as Tumblr has never had proper alt text support. Tumblr's reblogging system facilitates this kind of interaction in ways that other platforms do not, as another user’s image descriptions can be grouped with the original inaccessible image. Community accessibility improves the overall degree to which disabled users may participate in the community, without placing the burden on those disabled users themselves.
What would it look like if we encouraged and facilitated community accessibility when building our own platforms? Media-sharing platforms could allow users to submit accessibility requests for captions or alt text, or allow them to be added in later. (I can't count the number of times that I've forgotten to add alt text to a tweet.) Platforms that facilitate meetups or review services could allow users to enter accessibility information, so that people who use wheelchairs won't have to suffer the humiliation of turning up at an event only to find it completely inaccessible to them. At the very least, platforms could facilitate requests for accessibility information anonymously, as disabled users shouldn’t have to open themselves up to harassment from defensive event planners. The potential for improvement is limitless and we can do better at every step of the process, from idea conception to development.
In terms of tangible things that we can start to do, I have a few suggestions. For those of us in tech, we should reflect on the state of our projects, as SeriousEats has done in this thoughtful public blog post. For social media users, it may mean thinking about the ways we interact with social media (Tumblr or otherwise!), and committing to improving accessibility in the content we create. No matter what level of knowledge we have, above all we need to listen to and center the conversations that disabled people are having.
Building accessible platforms does not start when we begin designing user interfaces (UIs); we need to be thinking about this much earlier on in the process. Caring for the collective physical and mental health of our communities is integral to practicing disability justice. We cannot build platforms that are technically accessible but facilitate the harassment and ongoing traumatization of marginalized communities and then call our jobs done. The power of the internet is transformative, and when we make spaces inclusive, we change lives for the better.
]]>For those who might be unfamiliar, the Theme Customizer is basically Shopify's answer to a page builder. In the actual admin, most pages just present you with a RTE field and a few other settings, but through the customizer, developers can define 'sections' with their own fields or repeatable blocks to allow merchants to customize complex content and layout on their pages.
Don't get me wrong, I adore sections and the addition of them has been the single greatest addition to Shopify since I started working with the platform. The downside is that for now, only homepage sections can be added and reordered. Other pages must have their sections predefined and placed in order within the template code. Even more frustrating is that while sections on the homepage are unique, sections on other pages are shared. So while you may be able to have multiple instances of say, a hero image component on the homepage, if you wanted to have hero components on a 'Contact Us' and 'About Page' with different content, you would need to define separate sections for this. There are different techniques to work with this limitation, but all involve duplication of the schema, and it requires developer intervention for the creation of new customized pages, even if it's only to copy and paste sections to create new instances. Allowing the full use of sections on all pages would open up a ton of new interesting possibilities for designs without having to do anything hacky like processing the RTE output or adding shortcodes.
In addition to rolling out sections for all pages, it would be fantastic to be able to define some additional settings for sections, like what pages they can be added to. A list of product features might not make sense on non product pages, but it would still be nice to have it as a section so it can be reordered on the product page. It would also be useful to be able to define whether or not a section's content is 'global' or not. For example, you may have a list of products currently on sale on some pages in your store. In this case, it wouldn't make sense to have it be a part of your layout files, but you might want the content of that section to be the same any time it is used, and if it is updated on one page, it would be updated on all pages that it is present on.
While I'm on the topic of the the customizer, I think the image picker could use a redesign. I understand the desire to show the content being edited next to the images, but whenever I need to reuse an older image, the small image panel isn't much help. A search bar would go a long way in helping to track down existing images, and it would be even better if there was an option to look through past images in an expandable window, as seems a bit ridiculous to me that on my large external monitor, I can only see 9 thumbnails at a time. I often see editors re-upload the same image to save themselves the trouble of trying to find an older image, which means customers don't get the performance benefits of things like caching.
My team have heard me say this enough, but Shopify, it's 2019, please give us SVG upload support in the image picker! Vector-based illustrations and icons are a huge trend that is not going away any time soon, and it would be nice to allow people to upload crispy logos.
One final thing I would like for the image picker is the ability to force an image to have alt text added before it can be saved. I look forward to a day where we can leave image carousels in the past, but until we reach a point where clients stop putting critical information exclusively in images, we need processes to ensure that we are not leaving screen reader users out of the equation. Right now, adding alt text is very much optional and it can be easily forgotten by a site editor.
Ask any Wordpress developer what their favorite plugins are, and you'll find that Advanced Custom Fields is near the top of the list. ACF is a fantastic plugin that lets a developer add fields directly to the admin panel, allowing editors to edit complicated page layouts without fiddling with HTML or page builder plugins. Shopify's answer to this is ‘metafields’ — nearly all Shopify objects let you assign metafields to them. Metafields are often used by apps to extend Shopify's features, but they can also be used to populate unique content for specific products, pages, etc. For example, if you wanted to add an instructional video to your product pages, you could define a metafield for the embed code, and pull it in on the product templates.
The downside with Shopify is that there is no way to pull in metafields directly into the admin pages, you must use an external app. Because of the necessary authentication steps required for an external app, editing metafields can be a time consuming and annoying process. Some apps only let you edit the metafields that were created within that same app, which ties you to that particular app, but apps that show all metafields can show information that is unnecessary for the editor, like configuration objects for other app functions. Having the ability to define metafields that can be shown directly within the admin panel with a corresponding field type would significantly improve the editing experience for merchants.
One thing I enjoy about Shopify is the way they approach configuration. A store's settings (everything from what colors are used to the content for the sections) is stored in a single config/settings_data.json
file, which makes migration and backups easier as you don't need to keep track of a local and remote database. It's treated as any other file, which means that you can edit locally and have ThemeKit sync up your changes remotely, or pull down remote changes from the live store whenever needed. However, when it comes to keeping track of this file in Git and managing it within the context of an active store, we've hit several barriers that make this a lot more unwieldy than we'd like.
Much of Shopify's theme tooling has been built around the use case of building a theme that would be used by many stores or sold in the theme store. In cases like this, the developers can easily manage the theme. They do not need to be concerned with a specific store's configuration, and they can treat their repository as a single source of truth and push updates to the theme store.
In the case where a developer is actively maintaining an existing store, managing version control becomes a lot more difficult. Most developers will have a seperate theme that is forked from the currently live theme where they will be implementing new features. To track this in Git, you might have the master branch correspond to the currently live theme, and new feature branches being created to correspond to themes that are not currently live but can be previewed through the admin. While a developer is working on a new feature, the merchant may install and configure a bunch of new apps that directly modify the theme code, and rearrange the homepage sections to reflect a new promotion. The developer needs to be actively pulling all these new changes into Git, or they might be lost when the feature branches are merged. Even worse, a developer may actively be trying to stay on top of these changes, but still potentially lose client content when it comes to merging due to the way JSON works.
Below is a abridged snippet of code that demonstrates how Shopify stores data for content blocks within the config/settings_data.json
file. The numbers represent longer IDs that have been cut short for demonstration purposes, and each block represents a slide in a carousel.
"1": {
"type": "carousel",
"blocks": {
"1": {
...
},
"2": {
...
}
},
"block_order": [
"1",
"2"
],
}
As you can see, they are using a JavaScript Object for the block settings, and a separate block_order
Array to determine the what order the blocks come in. We recently had a case where the client added a slide to the homepage while we were working on a separate feature branch. When it came time to merge feature branch with the master branch, the JavaScript object with the blocks was reordered as there is no guaranteed order on key/value pairs. As a result, the merge ended up creating an invalid configuration object with two duplicate slides and we ended up 'losing' a slide.
Most developers (and Shopify themselves) utilize the approach of completely ignoring the config/settings_data.json
file in Git to avoid these hassles. This approach works if you are not servicing any particular store in general and are just developing themes, or if you are managing a store which lacks complex custom features and does not need to be updated often. But for large active stores, particularly Shopify Plus stores, better support for things like version control and workflow tooling are necessary to prevent potentially costly mistakes in the form of broken deployments and data loss. I've often encountered stores where developers are working directly on the live theme with no version control whatsoever, which can lead to disastrous consequences for the merchant. Improving the developer experience would allow developers to provide better service to merchants, and cultivate a healthier developer ecosystem around the Shopify platform in general.
Shopify has made great strides in improving accessibility in recent years, and I am very glad that they have a team devoted to working on improving the accessibility of their platform and their official themes. Currently, Debut and the Shopify Checkout paired together make for an experience that has been well tested by their accessibility team, so if it is a hard requirement that your store complies with the WCAG 2.1 guidelines, the Debut theme is a great place to start. Shopify also has a library of liquid snippets that serve as a great reference for anyone looking to implement common e-commerce patterns in a semantic and accessible way.
Shopify’s commitment to writing accessible code is great, but for now, third-party themes and apps remain a completely different ball game. The Shopify team have expressed that they are working on a set of guidelines for themes and apps to adhere to, and while I am very excited for this possibility, there’s not much out there right now to help well-meaning merchants find an accessible theme. Apps can be especially problematic, as a fairly accessible experience can be rendered completely inaccessible by something like a poorly-written email capture modal that offers no way to close it through keyboard access alone.
During the transition period for between now and whenever the accessibility guidelines are rolled out, it would be great to see functionality that would highlight more accessible themes and plugins within their respective stores, such as a special tag or something along those lines. Adding something like this would encourage theme developers to make accessibility a priority now, rather than waiting till when the guidelines come out and doing the bare minimum to meet them. Many merchants these days are more concerned about accessibility (even if only to avoid a facing a lawsuit), but right now, they lack the ability to make decisions about it without the guidance of an experienced technical person.
I hope that what you take from this article isn’t ‘Shopify is a crappy platform that needs a ton of fixes to be usable’. On the contrary, I think Shopify is a great platform that has continued to implement features to improve both the developer and merchant experience. I would like nothing more than to have solutions to all these problems announced at this year’s Shopify Unite conference so as developers, we can better meet the needs of not only our clients, but their customers as well.
If you're also a Shopify dev, I'd love to hear from you! What features would you like Shopify to implement?
]]>Over the years, as I learned more about web accessibility, and heard directly from disabled users giving feedback on my accessible Tumblr Theme, I began having the sneaking suspicion that my own site was probably not up to par. In 2018, I spent a lot of time focused on not only learning about accessible web practices, but also actually implementing accessibility improvements on client work, and now I finally feel ready to confront the skeletons in my own closet by tackling my own site.
Admittedly, I feared this day not because I felt I lacked the knowledge to implement certain accessibility changes, but because I felt that admitting any previous lack of knowledge would be perceived as a sign of weakness in a highly competitive industry. I toyed with the idea of wiping away all my past mistakes with a completely new design so I could pretend that I had always known the best way to build sites, but eventually I decided against this because 1. I still kind of like the design for this site and 2. I think it's more of an accurate representation to the way we build things on the web. Sure, we may not get it right the first time, or even the next few times, but the beauty of the web is that you get to go back and improve things over time. Everything is an ongoing work in progress, and we should embrace particular fact as a strength of the medium, not as a weakness. And on that note, feel free to continue reading to follow along on my journey to improving my own site.
Right off the bat, I knew the color scheme was probably going to fail. While the body text, a deep navy, would definitely pass, I had some places where I knew the color contrast was too low, and my project pages had small transparent white text against a brand color that may or may not have been chosen with contrast in mind. Using this fabulous tool, aremycolorsaccessible.com, I was able to input colors from my color scheme, and play with the toggles to find accessible alternatives and combinations. This tool is great because it not only tells you if your chosen colors are accessible, it also lets you go and tweak the color scheme directly on the same page, so you don't have to go back and forth between your graphics software and the browser.
Take Home Message: Color schemes and contrast play a significant role in determining whether or not a site is accessible. Tooling can be extremely helpful for determining appropriate color schemes.
One easy accessibility win is adding a 'Skip to Main Content' link. This is a link which targets the main element on the page, allowing keyboard users to easily bypass tabbing through the whole navigation on each page. Most modern screen readers are smart enough to allow you to skip navigation elements if they are properly marked up, but this is still a nice thing to have for sighted keyboard users.
I ended up giving a solid background to my navigation on all pages (as opposed to a transparent background), so that I would eliminate the possibility that I'd choose a background that provided too little contrast for my navigation links. I also made sure that aria-current="page"
was added to the currently active navigation link.
In addition, the existing hover and focus state of my navigation items needed improvement, as the current focus state relied solely on a color change. It is best not to rely exclusively on changing color in order to convey important information to the user, as certain types of color blindness can prevent people from easily detecting the change. I added an underline to the ensure that the currently highlighted nav item would not be missed.
Take Home Message: Navigation is a crucial component to get right. Opt for visible navigation over hidden navigation if possible, and do your best to ensure that you can use the keyboard to navigate the menu by using clear focus states and appropriate elements and aria-roles when applicable.
I had taken the time to ensure that the homepage, with it's fancy scroll effect and all, still worked without JavaScript enabled. For sites with fancy animation where the elements load in on page scroll, it's common to set the initial state of elements to be animated with CSS, and then apply the transition with JS once the user has scrolled down to that section of the page. Because these transitions often involve transitioning from 0 opacity or transitioning elements from off the page, it's a good idea to set some kind of fallback method so that in case JS fails to load for some reason, your users can still interact with the page.
While I had progressively enhanced the whole layout, I had missed some basic semantics in the markup that would make a big difference to the experience of screen reader users. For example, look at the code snippet below:
<div class="tags__wrapper">
<span class="tags__tag">Jekyll</span>
<span class="tags__tag">Forestry</span>
<span class="tags__tag">SCSS</span>
<span class="tags__tag">Javascript</span>
</div>
Spot the issue? We should probably list these items out as an unordered list instead. Newer developers will often reach for the markup that is easier to style instead of choosing the element that is the most semantic, and this has a tendency to hurt not only screen readers, but any kind of robot/crawler, including search engines. Using semantic elements for list elements can allow screen readers to say the the total number of elements and allow for easy navigation within the list elements, and it is worth it to take the time to do a little bit of extra styling in order to make sure that the elements are read correctly when read aloud by screen readers.
One more thing to note about the homepage - consider the markup below:
<a class="button button--small" href="/projects/studio-ryte">
Read Case Study →
</a>
Links that say things like 'Read More' or 'Learn More' are quite commonly seen across the web, but they can hurt scanability. Some screen readers offer the functionality of listing all links on a page. Vague link text or repeated link text can be confusing for users trying to scan for the link they want to navigate to when out of regular document context. Complicating things further, the title attribute is handled inconsistently between screen readers - some will ignore it completely and some read both the link text and the title attribute. We can provide context for screen reader users by adding screen reader text to the link, a trick I picked up from Lindsey Kopacz's excellent blog! The corrected markup is as follows.
<a class="button button--small" href="/projects/studio-ryte">
Read <span class="screenreader">Studio Ryte</span> Case Study →
</a>
Take Home Message: While the end result may all look the same to a sighted user, proper semantic markup gives you a ton of accessibility features for free. Consider the desired experience for your users and use the appropriate element based on that, not based on what is easiest to style.
The about page features a resume section in addition to a contact form. Below is a sample of some of the resume markup in Liquid.
<h2 class="resume__heading">Resume</h2>
<h2 class="subheading">Related Experience</h2>
{% for exp in page.experience %}
<span class="resume__institution">{{ exp.institution }}</span>
<span class="resume__title"> {{ exp.title }}</span>
<span class="resume__bullet">•</span>
<span class="resume__date">{{ exp.date }}</span>
<ul class="resume__description">
{% for desc in exp.description %}
<li>{{ desc.li }}</li>
{% endfor %}
</ul>
{% endfor %}
Right off the bat, we can see that I used two H2 tags in a row. 'Related Experience' is most definitely supposed to be a heading under 'Resume', and the two have separate classes with different styling to distinguish them visually, so the only cause I can think of is that I must have been copy and pasting from other parts of the project without really thinking about what heading level was appropriate in the context of the destination. 🤦
Screen readers allow users to navigate by heading levels, so having a clear document structure is necessary. With the markup above, it would appear that 'Resume' is a completely empty section to a screen reader user! We can fix this up by making 'Related Experience' an h3, and further improve readability by putting the position title, instution and date in a h4 to mark it as a heading before the list element below it.
<h2 class="resume__heading">Resume</h2>
<h3 class="subheading">Related Experience</h3>
{% for exp in page.experience %}
<h4 class="resume__point">
<span class="resume__institution">{{ exp.institution }}</span>
<span class="resume__title"> {{ exp.title }}</span>
<span class="resume__bullet">•</span>
<span class="resume__date">{{ exp.date }}</span>
</h4>
<ul class="resume__description">
{% for desc in exp.description %}
<li>{{ desc.li }}</li>
{% endfor %}
</ul>
{% endfor %}
Take Home Message: Proper use of headings is essential for screen readers. Always evaluate what heading level is appropriate in the context of the resulting page. Be especially careful when using frameworks/component libraries that may abstract over the generated markup, it's easy to reach for the element that matches the design and not realize that you are using an incorrect heading level.
The illustration section of my portfolio site is fairly simple in theory. Because the order of illustrations doesn't matter in this case, I have chosen to use CSS Multi-Column layout to create a Masonry style effect without loading any additional JS. Upon clicking an image, the image will open a modal that shows the full image and the title.
The modal was powered by Lokesh Dhakar's Lightbox 2 script, originally written over 8 years ago. The script used JavaScript to handle things that were difficult to do with CSS during that time period, like controlling the sizing and positioning of the image, and was ridiculously simple to set up. However, I realized the script failed on some basic accessibility issues, such as lacking alt text support, and failing to trap focus within the modal. An accessible modal should not only transfer focus to the modal when opened, it should also hide all other content to screen readers, and it needs to keep focus within itself until the element is closed. Otherwise, you can encounter an unpleasant situation where you can tab out of the modal, but the modal remains open, obscuring a keyboard user's current position on the page. I did some research by looking through the project's Github issues, but it seemed like accessibility fixes weren't a priority for this version of the project, and would be better handled in version 3.
I decided that instead of submitting a PR to a project that wasn't actively being worked on anymore, I would find a new tool that would help me replicate the lightbox functionality. Additionally, I could kill two birds with one stone and take steps to remove my site's dependency on jQuery. Honestly, finding component libraries that are explicitly focused on accessibility and aren't tied to some other JS framework, whether it be jQuery, React, or something else, can be quite difficult. I find myself returning to Heydon Pickering's Inclusive Components and Scott O'Hara's Accessible Components time and time again whenever I need to find good examples of common UI patterns.
In this situation, I ended up using the a11y-dialog library. I've used this library successfully on several client projects, and it exposes events that I need to dynamically generate the necessary markup. I like this library in particular because it comes with no styling - not a perk for most people, but this is great for projects when you need to match a client's designs and you don't want to spend hours overriding the highly specific CSS that often comes with these libraries! It uses the native <dialog>
when the browser supports that element, and makes it work on older browsers that don't support <dialog>
just yet.
I won't go into too much detail about my implementation, but there are a few things that are worth noting. I kept the modal trigger element as links to the larger versions of the images instead of a button, I like this as a form of progressive enhancement - if for some reason the JS fails, you can still see a larger version of the image by clicking on the link. I used flexbox on the contents of the dialog window, so that the title could be before the image in the source order, allowing the image to fall under the heading (a trick I picked up from Heydon's article about cards!). I then used the order property to visually move the title below the image, as is more conventional when displaying art, so both sighted and screen reader users will experience a structure they are more familiar with. Using flexbox and the :not(:empty)
selector allowed me to easily conditionally resize the image to occupy the full height if there wasn't a title (naming things is hard, as we all know). You can visit this commit on Github to check out the code for the dialog.
Scott O'Hara points out that the dialog element isn't quite functioning as intended in certain popular browser and screen reader combinations, and that it's best to avoid the <dialog>
element completely. In my code, I have replaced it with a <div>
(horrified gasps echo around the room) with the role of dialog. In cases like this where the native support isn't that great, an appropriate usage of ARIA labels can help. Modal dialogs are a particular difficult component to build, and the incomplete/quirky browser support only makes it more difficult. While I'm not using modals for a critical element of my site, if you are using them for mission critical UI, consider testing with as many screen reader and browser combinations as possible as they all have their own unique quirks.
Take Home Message: A single inaccessible dependency can render a site impossible to use for screen readers. Audit any UI libraries thoroughly before incorporating them into your project!
I hope this post has been somewhat helpful! Lately I have seen a greater push for web accessibility, and I wanted to contribute something that would provide a little more detail to those who already understand why accessibility is important, but are unsure how to begin implementing it. I'd like to close with some resources which have been incredibly valuable to me in my learning process:
]]>See the Pen Animated Dividers by Nic Chan (@nchan0154) on CodePen.
The canvas API is fairly primitive in that once you draw a shape, you can't really go back and edit it. This is the exact opposite of CSS or Javascript animation frameworks like GSAP, where you can reach in and grab a particular element with the targeted use of selectors. Because of this, the typical animation flow goes something like this:
It's pretty amazing to think that with every frame of this animation, we're drawing the same lines over again, eh? MDN was an invaluable source for me when I was trying to understand this process.
I spent a lot of time trying to come up with the perfect formulas for calculating things like opacity, speed, and scaling. If we were modelling a real life situation, getting an accurate algorithm would be top priority, but since we are merely using these functions to generate a visual representation, there is a lot more freedom! Getting something that just 'feels right' is perfectly acceptable, and in some cases, preferred, over trying to manually calculate that perfect bezier curve.
I admit I'm a little ashamed to post this, but above is my first attempt at trying to generate a pattern of network lines with Javascript. It wasn't even in the same ballpark as what we needed, and I used exactly none of the code in the final product. Regardless, it's an important reminder that experimentation is necessary - you will probably fail your first few attempts when attempting a new thing, and that's perfectly normal.
If you want to see it in action, you can check out the final animation out on the Darkshore Website.
]]>Note, I am not smart enough to have figured any of this out on my own, and I make no promises that this will work perfectly on your machine. It's just what worked for my machine. All sources are linked.
Before we begin, it's advisable to prepare a backup version of Windows, especially if you have any data on there that might be of value. Because I was using a brand new machine and I live on the edge (not the Microsoft Edge, geddit?), I chose to forgo this step.
You will need a clean USB with at least 4GB of space. I first grabbed Ubuntu 16.04.01 from the official Ubuntu Website. I then downloaded Rufus as recommended in their docs to format the USB. Ubuntu has prepared an excellent guide on how to do this, but basically you are selecting your USB stick and your freshly downloaded Ubuntu installation and writing it in ISO mode.
Since my machine came with Windows 10 preinstalled, I needed to adjust the SSD configuration as Ubuntu wouldn't recognize the hard drive in RAID mode.
Following this discussion thread, I ran the following steps:
Run as Administrator
bcdedit /set {current} safeboot minimal
Bios Setup
System Configuration
and select SATA operation
RAID on
to AHCI
bcdedit /deletevalue {current} safeboot
to disable safe mode bootAlright, time to shrink down our windows partition to make space for Linux!
Disk Management
(or open Command Prompt and type diskmgmt.msc
)Shrink Volume
Alright, it's show time! Plug your bootable Ubuntu USB in, restart your computer once more, hit F12
and choose to Install Ubuntu from your USB. The installation process is fairly simple, you can follow Ubuntu's Official Guide. Because we are dual booting, I chose to install it alongside Windows boot manager. Now that we've unallocated some space for Ubuntu and switched to AHCI mode, it should recognize the unallocated space as an option and install it to that space. Whew! The hard part is over.
The first thing I noticed on my new Ubuntu install was that the trackpad didn't have palm detection turned on, and it was making typing a nightmare. To resolve it, I installed Libinput and configured it with the following steps:
sudo apt-get install xserver-xorg-input-libinput
gedit /usr/share/X11/xorg.conf.d/90-libinput.conf
(or vi if you prefer vim). It may be located in a different location depending on your Ubuntu installation.Section "InputClass"
Identifier "libinput touchpad catchall"
MatchIsTouchpad "on"
MatchDevicePath "/dev/input/event*"
Driver "libinput"
Option "Tapping" "True"
Option "PalmDetection" "True"
Option "TappingDragLock" "True"
EndSection
Phew! Now we can finally type without wanting to pull our own hair out.
What? We're still not done? :( There's one more thing we need to fix before we log back into Windows again. There tends to be conflicts with how the time is stored when running Windows and Linux on the same machine. Luckily, it's an easy fix if you want to run Linux on local time as opposed to UTC. On our version of Ubuntu, simply open up the terminal and type timedatectl set-local-rtc 1
. The Ubuntu Website offers alternative solutions if you prefer.
Now we're finally done. Happy developing!
]]>Since we are unable to transition between gradients using CSS, some trickery was required. For the animated bar part, I set the background to a linear gradient between 3 colors, then sizing up the background to 200% in width, but 100% in height. To achieve the effect of a gradient transitioning, I transformed the background position back and forth.
The little nodes on the end of the divider are nothing special, simple pseudo-elements, but they certainly add to the network theme of the site, don't you think?
See the Pen Animated Dividers by Nic Chan (@nchan0154) on CodePen.
]]>Because Shopify doesn't let you download their software, what I recommend doing is signing up for a Partner Account, which lets you create unlimited test stores (as well as pick up a percentage of profits if you refer customers to the platform). Rather than manually upload your theme files to your test store every time, you can either use the Shopify Theme Gem or the Mac Only Desktop Theme Editor to sync all your theme code directly to the test store. I chose the gem - the set up instructions included in the repository were very straightforward and it didn't take long before I was up and running.
Once your theme watch
is up and running, you can choose to go with an existing Shopify starter theme to help speed up your development process. Skeleton is the more minimal of Shopify's two official themes, with the bare minimum amount of boilerplate to get you started. Timber is the one I ended up using, it includes a helper class based grid system, an AJAX based interactive cart and responsive menus. If you'd prefer to roll with the more popular Foundation or Bootstrap, it might be wiser to go with the skeleton theme.
A shopify theme has the following folders:
If you poke around the assets folder, you might notice that all the styling is done in one giant .scss.liquid file, which Shopify compiles on its own servers. If you're used to a modern, modularized CSS workflow, the ban against @import
will seem like a nightmare. Running your own SCSS processor and uploading the compiled CSS is an option, but doing so won't let you use liquid variables (aka Shopify settings you might want to include like colors and font sizes) within your SCSS. What I ended up doing was running a gulp task that watched a newly created scss folder. Upon detecting changes to the contents of that folder, it would use gulp-cssimport
to string my stylesheets together into one giant .scss.liquid file that it then dumped into the assets directory. Below is my Gulpfile.
var gulp = require('gulp');
var cssimport = require("gulp-cssimport");
var globalConfig = {
src: 'scss' // The directory where all your SCSS is
};
// Process CSS
gulp.task('styles', function(){
return gulp.src(globalConfig.src + '/style.scss.liquid')
.pipe(cssimport())
.pipe(gulp.dest('assets/'));
})
// Watch files
gulp.task('watch', function () {
gulp.watch(globalConfig.src + '/**/*.*', ['styles']);
});
// Default task
gulp.task('default', ['watch']);
And my style.scss.liquid looked something like this:
@import url('_variables.scss.liquid');
@import url('libs/_timber.scss.liquid');
...
@import url('components/_header.scss.liquid');
...
This way, you get to structure your scss however you are used to doing it. Crisis averted!
Those of you coming from Wordpress will be pleased to know that adding new settings to Shopify is as easy as changing the config/settings_schema.json
file. Below is a snippet from mine.
{
"name": "Colors",
"settings": [
{
"type": "header",
"content": "TNB Theme Colors"
},
{
"type": "color",
"id": "tnb_color_bg",
"label": "Background Color",
"default": "#222222",
"info": "Background color"
}
]
}
This setting can be accessed in your .scss.liquid files just as easily, like so {{settings.tnb_color_bg}}
.
While I found developing for Shopify to be a breath of fresh air compared to working with Wordpress (figuring out how to handle theme settings + SCSS in Wordpress is just plain shitty), I have a few caveats. Shopify doesn't let you customize the checkout.liquid template (the template that handles the checkout process) unless you are a Shopify Plus customer. It also doesn't make it particularly easy to filter by product variants, which is the standard way to assign things like colors and sizes to a product. Those who might want complete control over the entire user experience or offer complicated product filtering without resorting to workarounds like manually managing tags should seek out a more robust platform, but if ease to use is a priority, I definitely wholeheartedly recommend Shopify.
]]>